Latenze TCP più elevate nelle ultime versioni di Linux


8

Nel mio gruppo di ricerca, abbiamo recentemente aggiornato il sistema operativo sulle nostre macchine da Red Hat 6.2 a Debian 8.3 e osservato che il tempo di andata e ritorno TCP attraverso le schede di rete integrate Intel 1G tra le nostre macchine era raddoppiato da circa 110µs a 220µs.

All'inizio, ho pensato che fosse un problema di configurazione, quindi ho copiato tutte le configurazioni di sistema (come tcp_low_latency=1) dalle macchine Red Hat non aggiornate alle macchine Debian e che non ha risolto il problema. Successivamente, ho pensato che potesse trattarsi di un problema di distribuzione Linux e ho installato Red Hat 7.2 sulle macchine, ma i tempi di andata e ritorno sono rimasti di circa 220µs.

Infine, ho pensato che forse il problema riguardava le versioni del kernel Linux da quando Debian 8.3 e Red Hat 7.2 avevano entrambi usato il kernel 3.x mentre Red Hat 6.2 ha usato il kernel 2.6. Quindi per provarlo, ho installato Debian 6.0 con kernel Linux 2.6 e bingo! I tempi erano di nuovo rapidi a 110µs.

Altri hanno anche sperimentato queste latenze più elevate nelle ultime versioni di Linux e ci sono soluzioni alternative conosciute?


Esempio minimo di lavoro

Di seguito è un'applicazione C ++ che può essere utilizzata per confrontare la latenza. Misura la latenza inviando un messaggio, aspettando una risposta e quindi inviando il messaggio successivo. Lo fa 100.000 volte con messaggi a 100 byte. Pertanto, possiamo dividere il tempo di esecuzione del client per 100.000 per ottenere le latenze di andata e ritorno. Per utilizzare questa prima compilare il programma:

g++ -o socketpingpong -O3 -std=c++0x Server.cpp

Quindi esegui la versione dell'applicazione lato server su un host (ad esempio 192.168.0.101). Specifichiamo l'IP per garantire che stiamo ospitando su un'interfaccia ben nota.

socketpingpong 192.168.0.101

Quindi utilizzare l'utilità Unix timeper misurare il tempo di esecuzione del client.

time socketpingpong 192.168.0.101 client

L'esecuzione di questo esperimento tra due host Debian 8.3 con hardware identico produce i seguenti risultati.

real  0m22.743s
user  0m0.124s
sys     0m1.992s

I risultati di Debian 6.0 sono

real    0m11.448s 
user    0m0.716s  
sys     0m0.312s  

Codice:

#include <unistd.h>
#include <limits.h>
#include <string.h>

#include <linux/futex.h>
#include <arpa/inet.h>

#include <algorithm>

using namespace std;

static const int PORT = 2444;
static const int COUNT = 100000;

// Message sizes are 100 bytes
static const int SEND_SIZE = 100;
static const int RESP_SIZE = 100;

void serverLoop(const char* srd_addr) {
    printf("Creating server via regular sockets\r\n");
    int sockfd, newsockfd;
    socklen_t clilen;
    char buffer[SEND_SIZE];
    char bufferOut[RESP_SIZE];
    struct sockaddr_in serv_addr, cli_addr;

    sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (sockfd < 0)
       perror("ERROR opening socket");

    bzero((char *) &serv_addr, sizeof(serv_addr));
    serv_addr.sin_family = AF_INET;
    serv_addr.sin_addr.s_addr = inet_addr(srd_addr);
    serv_addr.sin_port = htons(PORT);

    fflush(stdout);
    if (bind(sockfd, (struct sockaddr *) &serv_addr,
             sizeof(serv_addr)) < 0) {
             perror("ERROR on binding");
    }

    listen(sockfd, INT_MAX);
    clilen = sizeof(cli_addr);
    printf("Started listening on %s port %d\r\n", srd_addr, PORT);
    fflush(stdout);

    while (true) {
        newsockfd = accept(sockfd, (struct sockaddr *) &cli_addr, &clilen);
        if (newsockfd < 0)
             perror("ERROR on accept");
        printf("New connection\r\n");

        int status = 1;
        while (status > 0) {
            // Read
            status = read(newsockfd, buffer, SEND_SIZE);
            if (status < 0) {
                perror("read");
                break;
            }

            if (status == 0) {
                printf("connection closed");
                break;
            }

            // Respond
            status = write(newsockfd, bufferOut, RESP_SIZE);
            if (status < 0) {
                perror("write");
                break;
            }
        }

        close(newsockfd);
    }


    close(sockfd);
}

int clientLoop(const char* srd_addr) {
    // This example is copied from http://www.binarytides.com/server-client-example-c-sockets-linux/
    int sock;
    struct sockaddr_in server;
    char message[SEND_SIZE] , server_reply[RESP_SIZE];

    //Create socket
    sock = socket(AF_INET , SOCK_STREAM , 0);
    if (sock == -1)
    {
        printf("Could not create socket");
    }
    puts("Socket created");

    server.sin_addr.s_addr = inet_addr(srd_addr);
    server.sin_family = AF_INET;
    server.sin_port = htons( PORT );

    //Connect to remote server
    if (connect(sock , (struct sockaddr *)&server , sizeof(server)) < 0)
    {
        perror("connect failed. Error");
        return 1;
    }

    printf("Connected to %s on port %d\n", srd_addr, PORT);

    // Fill buffer
    for (int i = 0; i < SEND_SIZE; ++i) {
        message[i] = 'a' + (i % 26);
    }

    for (int i = 0; i < COUNT; ++i) {
        if (send(sock, message, SEND_SIZE, 0) < 0) {
            perror("send");
            return 1;
        }

        if ( recv(sock, server_reply, RESP_SIZE, 0) < 0) {
            perror("recv");
            return 1;
        }
    }

    close(sock);

    printf("Sending %d messages of size %d bytes with response sizes of %d bytes\r\n",
            COUNT, SEND_SIZE, RESP_SIZE);
    return 0;
}

int main(int argc, char** argv) {
    if (argc < 2) {
        printf("\r\nUsage: socketpingpong <ipaddress> [client]\r\n");
        exit(-1);
    }
    if (argc == 2)
        serverLoop(argv[1]);
    else
        clientLoop(argv[1]);
    return 0;
}

2
Cosa ha spinto il passaggio da Redhat a Debian ? Sul lato Redhat, ci sono più strumenti e utilità per aiutare a risolvere problemi come questo.
ewwhite,

1
Vorrei contattare la mailing list del kernel Linux o (se ce l'hai) supporto Red Hat. Potrebbero saperlo, e in caso contrario ci saranno persone che sono tutte impostate per "dividere in due" le modifiche al codice del kernel per scoprire da dove provengono i bug.
Legge del

Penso che dovresti usare qualche strumento (gprof, Valgrind o gperftools) per profilare il tuo codice.
Jose Raul Barreras,

Cosa succede se disabiliti l'algoritmo di nagle su entrambi client / server? int ndelay = 1; setsockopt (<socket>, IPPROTO_TCP, TCP_NODELAY, & flag, sizeof (int)); - le differenze persistono? Inoltre - è solo per TCPC? cioè per icmp / ping osservi lo stesso?
Kjetil Joergensen,

1
Inoltre - c'è qualche differenza nelle impostazioni di coalescenza o di offload tra "veloce" e "lento"? ethtool -c <dev> ed ethtool -k <dev>. I valori predefiniti del driver potrebbero essere cambiati.
Kjetil Joergensen,

Risposte:


1

Questa non è una risposta, ma è importante calibrare rigorosamente i problemi di latenza / throughput. Potrebbe aiutarti ad avvicinarti alla risposta e persino aiutare gli altri qui a darti suggerimenti migliori sul processo che causa la radice.

Prova ad ottenere dati più precisi con un Wireshark cattura / tshark sull'interfaccia a,

  1. Verificare che il throughput sia effettivamente dimezzato e
  2. Identificare come viene distribuita la latenza (tra tx e rx)
    a. è uniforme durante il test?
    b. c'è una stalla ingombra da qualche parte?

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.