6
votes

I have implemented a small benchmark for socket communication via TCP and UDP in Python. Surprisingly, TCP is almost exactly double as fast as UDP.

To avoid routing effects, server and client are running on the same Unix machine, but on different threads.

Maybe the code is useful. Here is the server code:

import socket
import sys

host = 'localhost'  
port = 8888
buffersize = 8
server_address = (host, port) 

def start_UDP_server():
    socket_UDP = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    socket_UDP.bind(server_address)

    print("UDP server is running...")

    while True:
        data, from_address = socket_UDP.recvfrom(buffersize)
        if not data: break
        socket_UDP.sendto(data, from_address)
    socket_UDP.close()


def start_TCP_server():
    socket_TCP = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    socket_TCP.bind(server_address)
    socket_TCP.listen(1)

    print("TCP server is running...")

    while True:    
        client, client_address = socket_TCP.accept()

        while True:
            data = client.recv(buffersize)
            if not data: break
            client.sendall(data)

        client.close()

So you can run either start_TCP_server() or start_UDP_server().

On client side the code is:

import socket
import sys
import time

host = 'localhost'  
port = 8888
buffersize = 8
server_address = (host, port) 
client_address = (host, port+1)
N = 1000000


def benchmark_UDP():
    socket_UDP = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) 
    socket_UDP.bind(client_address)

    print("Benchmark UDP...")

    duration = 0.0
    for i in range(0, N):
        b = bytes("a"*buffersize, "utf-8")
        start = time.time()
        socket_UDP.sendto(b, server_address)
        data, from_address = socket_UDP.recvfrom(buffersize)
        duration += time.time() - start

        if data != b:
            print("Error: Sent and received data are bot the same")

    print(duration*pow(10, 6)/N, "µs for UDP") 


def benchmark_TCP():
    socket_TCP = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    socket_TCP.connect(server_address)

    print("Benchmark TCP...")

    duration = 0.0
    for i in range(0, N):
        b = bytes("a"*buffersize, "utf-8")
        start = time.time()
        socket_TCP.sendall(b)
        data = socket_TCP.recv(buffersize)
        duration += time.time() - start

        if data != b:
            print("Error: Sent and received data are bot the same")

    print(duration*pow(10, 6)/N, "µs for TCP")
    socket_TCP.close()

Like for the server you can start the benchmark by benchmark_TCP() or benchmark_UDP().

The results are about 25 µs for TCP, and about 54 µs for UDP on Unix and even worse for Windows (about 30 µs for TCP and more than 200 µs for UDP). Why? I would expect a minimal advantage for UDP.

1
Could be related to the size of the data you're sending and MTU size see hereGerrat
No, because my question is not about throughput, but the software overhead (avoiding network configuration issues, etc.). I want to measure a ping-pong message 10^6 times and not how many bytes can be pressed through the wire. Does this make it more clear?Michael Dorner
This is a bit apples and oranges. For UDP, you sending to a socket each time, which means resolving hostname to IP address, etc. for every send/receive. For TCP you setup the connection once and then send data over that connection each time. It might be more like Tangerines vs Seville if for TCP you put the connection setup/close in the loop. But either way your single latency metric only reflects one aspect of UDP vs TCP - the latency - but there are other factors like reliability and throughput which can also be important.barny
With regard to the comment from barny, jack up the buffersize to 64000 and see who's winning TCP or UDPRolf of Saxony
Rolf of Saxony - "winning" - if you only want to send 32 bytes, what's the point of measuring performance with 64k and using that to declare a winner? @Michale Dorner's test also skims over the fact UDP is probably less reliable than TCP - but a test within a single machine is very unlikely to expose that difference in reliability of delivery. That's metrics for you - you need to understand the circumstances of the measurement before you can start to compare the results.barny

1 Answers

4
votes

Your TCP socket is connected but your UDP socket is not. This means extra processing for every send/receive on the UDP socket. Call connect on each side for the UDP socket, just like you call connect/accept on the TCP socket.

Programs like iperf do this to measure accurately.