Latences TCP plus élevées dans les dernières versions de Linux

8

Dans mon groupe de recherche, nous avons récemment mis à niveau le système d'exploitation de nos machines de Red Hat 6.2 vers Debian 8.3 et observé que le temps d'aller-retour TCP via les cartes réseau Intel 1G intégrées entre nos machines avait doublé, passant d'environ 110 µs à 220 µs.

Au début, je pensais que c'était un problème de configuration, j'ai donc copié toutes les configurations sysctl (telles que tcp_low_latency=1) des machines Red Hat non mises à niveau vers les machines Debian et cela n'a pas résolu le problème. Ensuite, j'ai pensé qu'il s'agissait peut-être d'un problème de distribution Linux et j'ai installé Red Hat 7.2 sur les machines, mais les temps d'aller-retour sont restés autour de 220µs.

Enfin, je me suis dit que le problème venait peut-être des versions du noyau Linux puisque Debian 8.3 et Red Hat 7.2 avaient tous deux utilisé le noyau 3.x tandis que Red Hat 6.2 utilisait le noyau 2.6. Donc, pour tester cela, j'ai installé Debian 6.0 avec le noyau Linux 2.6 et le bingo! Les temps étaient à nouveau rapides à 110µs.

D'autres ont-ils également connu ces latences plus élevées dans les dernières versions de Linux, et existe-t-il des solutions de contournement connues?


Exemple de travail minimum

Vous trouverez ci-dessous une application C ++ qui peut être utilisée pour comparer la latence. Il mesure la latence en envoyant un message, en attendant une réponse, puis en envoyant le message suivant. Il le fait 100 000 fois avec des messages de 100 octets. Ainsi, nous pouvons diviser le temps d'exécution du client par 100 000 pour obtenir les latences aller-retour. Pour utiliser cette première compilation du programme:

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

Exécutez ensuite la version côté serveur de l'application sur un hôte (par exemple, 192.168.0.101). Nous spécifions l'IP pour nous assurer que nous hébergeons sur une interface bien connue.

socketpingpong 192.168.0.101

Et puis utilisez l'utilitaire Unix timepour mesurer le temps d'exécution du client.

time socketpingpong 192.168.0.101 client

L'exécution de cette expérience entre deux hôtes Debian 8.3 avec un matériel identique donne les résultats suivants.

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

Les résultats de Debian 6.0 sont

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

Code:

#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;
}
Stephen
la source
2
Qu'est-ce qui a incité le passage de Redhat à Debian ? Du côté de Redhat, il existe d'autres outils et utilitaires pour vous aider à résoudre des problèmes comme celui-ci.
ewwhite
1
Je contacterais la liste de diffusion Linux Kernel ou (si vous l'avez) le support Red Hat. Ils le savent peut-être, et s'ils ne le font pas, il y aura des gens qui sont tous configurés pour "bissecter" les modifications du code du noyau pour savoir d'où viennent les bogues.
Law29
Je pense que vous devriez utiliser un outil (gprof, Valgrind ou gperftools) pour profiler votre code.
Jose Raul Barreras
Que se passe-t-il si vous désactivez l'algorithme de nagle sur le client / serveur? int ndelay = 1; setsockopt (<socket>, IPPROTO_TCP, TCP_NODELAY, & flag, sizeof (int)); - la ou les différences persistent-elles? Aussi - est-ce juste pour TCP? ie pour icmp / ping observez-vous la même chose?
Kjetil Joergensen
1
Aussi - y a-t-il des différences dans les paramètres de fusion ou de déchargement entre "rapide" et "lent"? ethtool -c <dev> et ethtool -k <dev>. Les paramètres par défaut du pilote peuvent avoir changé.
Kjetil Joergensen

Réponses:

1

Ce n'est pas une réponse mais il est important de calibrer rigoureusement les problèmes de latence / débit. Cela pourrait vous aider à vous rapprocher de la réponse et même aider les autres ici à vous donner de meilleures suggestions sur le processus à l'origine des racines.

Essayez d'obtenir des données plus précises avec une capture Wireshark / Tshark sur l'interface pour,

  1. Confirmez que le débit est réellement divisé par deux et
  2. Identifier la répartition de la latence (entre tx et rx)
    a. est-il uniforme sur le test?
    b. y a-t-il un stand regroupé quelque part?
nik
la source