Latências TCP mais altas nas versões mais recentes do Linux

8

Em meu grupo de pesquisa, recentemente atualizamos o sistema operacional em nossas máquinas do Red Hat 6.2 para o Debian 8.3 e observamos que o tempo de ida e volta do TCP através das NICs Intel 1G integradas entre nossas máquinas dobrou de cerca de 110µs para 220µs.

No começo, eu pensei que era um problema de configuração, então copiei todas as configurações sysctl (como tcp_low_latency=1) das máquinas Red Hat não atualizadas para as máquinas Debian e isso não resolveu o problema. Em seguida, pensei que isso poderia ter sido um problema de distribuição do Linux e instalei o Red Hat 7.2 nas máquinas, mas os tempos de ida e volta permaneceram em torno de 220µs.

Finalmente, imaginei que talvez o problema estivesse nas versões do kernel Linux, já que o Debian 8.3 e o Red Hat 7.2 haviam usado o kernel 3.x, enquanto o Red Hat 6.2 usava o kernel 2.6. Então, para testar isso, instalei o Debian 6.0 com o kernel 2.6 do Linux e o bingo! Os tempos foram rápidos novamente a 110µs.

Outros também tiveram essas latências mais altas nas versões mais recentes do Linux e existem soluções alternativas conhecidas?


Exemplo Mínimo de Trabalho

Abaixo está um aplicativo C ++ que pode ser usado para comparar a latência. Ele mede a latência enviando uma mensagem, aguardando uma resposta e enviando a próxima mensagem. Faz isso 100.000 vezes com mensagens de 100 bytes. Assim, podemos dividir o tempo de execução do cliente por 100.000 para obter as latências de ida e volta. Para usar isso, primeiro compile o programa:

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

Em seguida, execute a versão do aplicativo do lado do servidor em um host (por exemplo, 192.168.0.101). Especificamos o IP para garantir que estamos hospedando em uma interface conhecida.

socketpingpong 192.168.0.101

E, em seguida, use o utilitário Unix timepara medir o tempo de execução do cliente.

time socketpingpong 192.168.0.101 client

A execução deste experimento entre dois hosts Debian 8.3 com hardware idêntico fornece os seguintes resultados.

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

Os resultados do Debian 6.0 são

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
fonte
2
O que levou a mudança do Redhat para o Debian ? No lado Redhat, existem mais ferramentas e utilitários para ajudar a resolver problemas como este.
ewwhite
1
Eu entraria em contato com a lista de discussão do Linux Kernel ou (se houver) com o suporte da Red Hat. Eles podem saber, e se não o fizerem, haverá pessoas configuradas para "dividir" as alterações no código do kernel para descobrir de onde vêm os erros.
Law29
Eu acho que você deve usar alguma ferramenta (gprof, Valgrind ou gperftools) para criar um perfil do seu código.
Jose Raul Barreras
O que acontece se você desabilitar o algoritmo de nagle no cliente / servidor? int ndelay = 1; setsockopt (<soquete>, IPPROTO_TCP, TCP_NODELAY e sinalizador, sizeof (int)); - as diferenças persistem? Além disso - isso é apenas para tcp? ou seja, para icmp / ping você observa o mesmo?
Kjetil Joergensen 9/09/16
1
Além disso - há alguma diferença nas configurações de coalescência ou descarga entre "rápido" e "lento"? ethtool -c <dev> e ethtool -k <dev>. O padrão do driver pode ter sido alterado.
Kjetil Joergensen 9/09/16

Respostas:

1

Essa não é uma resposta, mas é importante calibrar rigorosamente os problemas de latência / taxa de transferência. Isso pode ajudá-lo a se aproximar da resposta e até ajudar outras pessoas aqui a oferecer melhores sugestões sobre o processo causador da raiz.

Tente obter dados mais precisos com uma captura wireshark / tshark na interface para,

  1. Confirme se a taxa de transferência está realmente reduzida pela metade e
  2. Identifique como a latência é distribuída (entre tx e rx)
    a. é uniforme durante o teste?
    b. existe uma barraca em algum lugar?
nik
fonte