Latencias TCP más altas en las últimas versiones de Linux

8

En mi grupo de investigación, recientemente actualizamos el sistema operativo en nuestras máquinas de Red Hat 6.2 a Debian 8.3 y observamos que el tiempo de ida y vuelta TCP a través de las NIC Intel 1G integradas entre nuestras máquinas se había duplicado de aproximadamente 110 µs a 220 µs.

Al principio, pensé que era un problema de configuración, así que copié todas las configuraciones de sysctl (como tcp_low_latency=1) de las máquinas Red Hat sin actualizar a las máquinas Debian y eso no solucionó el problema. Luego, pensé que esto podría haber sido un problema de distribución de Linux e instalé Red Hat 7.2 en las máquinas, pero los tiempos de ida y vuelta se mantuvieron alrededor de 220 µs.

Finalmente, pensé que tal vez el problema era con las versiones del kernel de Linux ya que Debian 8.3 y Red Hat 7.2 habían usado el kernel 3.x mientras que Red Hat 6.2 usaba el kernel 2.6. ¡Para probar esto, instalé Debian 6.0 con Linux kernel 2.6 y bingo! Los tiempos fueron rápidos nuevamente a 110 µs.

¿Otros también han experimentado estas latencias más altas en las últimas versiones de Linux, y existen soluciones alternativas?


Ejemplo de trabajo mínimo

A continuación se muestra una aplicación C ++ que se puede utilizar para comparar la latencia. Mide la latencia enviando un mensaje, esperando una respuesta y luego enviando el siguiente mensaje. Lo hace 100,000 veces con mensajes de 100 bytes. Por lo tanto, podemos dividir el tiempo de ejecución del cliente por 100,000 para obtener las latencias de ida y vuelta. Para usar esta primera compilación del programa:

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

A continuación, ejecute la versión del lado del servidor de la aplicación en un host (por ejemplo, en 192.168.0.101). Especificamos la IP para garantizar que estamos alojando en una interfaz conocida.

socketpingpong 192.168.0.101

Y luego use la utilidad Unix timepara medir el tiempo de ejecución del cliente.

time socketpingpong 192.168.0.101 client

Ejecutar este experimento entre dos hosts Debian 8.3 con hardware idéntico da los siguientes resultados.

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

Los resultados de Debian 6.0 son

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

Código:

#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
fuente
2
¿Qué provocó el cambio de Redhat a Debian ? En el lado de Redhat, hay más herramientas y utilidades para ayudar a resolver problemas como este.
ewwhite
1
Me pondría en contacto con la lista de correo del kernel de Linux o (si lo tiene) con el soporte de Red Hat. Es posible que lo sepan, y si no lo hacen, habrá personas que estén configuradas para "bisecar" los cambios en el código del kernel para averiguar de dónde provienen los errores.
Ley29
Creo que debería usar alguna herramienta (gprof, Valgrind o gperftools) para perfilar su código.
Jose Raul Barreras
¿Qué sucede si deshabilita el algoritmo de nagle en ambos clientes / servidores? int ndelay = 1; setsockopt (<socket>, IPPROTO_TCP, TCP_NODELAY, y flag, sizeof (int)); - ¿persiste la (s) diferencia (s)? Además, ¿esto es solo para tcp? es decir, para icmp / ping ¿observas lo mismo?
Kjetil Joergensen
1
Además, ¿hay alguna diferencia en la configuración de fusión o descarga entre "rápido" y "lento"? ethtool -c <dev> y ethtool -k <dev>. Los valores predeterminados del controlador pueden haber cambiado.
Kjetil Joergensen

Respuestas:

1

Esta no es una respuesta, pero es importante calibrar rigurosamente los problemas de latencia / rendimiento. Puede ayudarlo a acercarse a la respuesta e incluso ayudar a otros aquí a darle mejores sugerencias sobre el proceso que causa la raíz.

Intente obtener datos más precisos con una captura de wireshark / tshark en la interfaz para,

  1. Confirme que el rendimiento se reduce a la mitad y
  2. Identifique cómo se distribuye la latencia (entre tx y rx)
    a. ¿Es uniforme durante la prueba?
    si. ¿hay algún puesto de trabajo en alguna parte?
nik
fuente