Более высокая задержка TCP в последних версиях Linux

8

В моей исследовательской группе мы недавно обновили ОС на наших машинах с Red Hat 6.2 до Debian 8.3 и заметили, что время прохождения TCP в обоих направлениях через встроенные сетевые адаптеры Intel 1G между нашими машинами удвоилось с примерно 110 мкс до 220 мкс.

Сначала я подумал, что это проблема с конфигурацией, поэтому я скопировал все конфигурации sysctl (например tcp_low_latency=1) с не обновленных машин Red Hat на машины Debian, и это не решило проблему. Затем я подумал, что это может быть проблема с дистрибутивом Linux и установил Red Hat 7.2 на машины, но время прохождения туда и обратно оставалось около 220 мкс.

Наконец, я решил, что, возможно, проблема была в версиях ядра Linux, поскольку Debian 8.3 и Red Hat 7.2 использовали ядро ​​3.x, а Red Hat 6.2 использовало ядро ​​2.6. Чтобы проверить это, я установил Debian 6.0 с ядром Linux 2.6 и бинго! Времена снова были быстрыми - 110 мкс.

Другие также испытывали такие большие задержки в последних версиях Linux, и есть ли известные обходные пути?


Минимальный рабочий пример

Ниже приведено приложение C ++, которое можно использовать для измерения задержки. Он измеряет задержку, отправляя сообщение, ожидая ответа, а затем отправляя следующее сообщение. Он делает это 100 000 раз со 100-байтовыми сообщениями. Таким образом, мы можем разделить время выполнения клиента на 100 000, чтобы получить задержки в оба конца. Чтобы использовать это сначала скомпилируйте программу:

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

Затем запустите серверную версию приложения на хосте (скажем, 192.168.0.101). Мы указываем IP-адрес, чтобы обеспечить хостинг на хорошо известном интерфейсе.

socketpingpong 192.168.0.101

А затем используйте утилиту Unix timeдля измерения времени выполнения клиента.

time socketpingpong 192.168.0.101 client

Выполнение этого эксперимента между двумя хостами Debian 8.3 с одинаковым оборудованием дает следующие результаты.

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

Результаты Debian 6.0

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

Код:

#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
Что побудило перейти с Redhat на Debian ? На стороне Redhat есть больше инструментов и утилит, помогающих справиться с такими проблемами.
Ewwhite
1
Я бы связался со списком рассылки Linux Kernel или (если он у вас есть) с Red Hat. Они могут знать, и если они этого не сделают, найдутся люди, которые все настроены на «деление» изменений кода ядра, чтобы выяснить, откуда происходят ошибки.
Law29
Я думаю, что вы должны использовать какой-то инструмент (gprof, Valgrind или gperftools) для профилирования вашего кода.
Хосе Рауль Баррерас
Что произойдет, если вы отключите алгоритм Nagle на клиент-сервере? int ndelay = 1; setsockopt (<socket>, IPPROTO_TCP, TCP_NODELAY, & flag, sizeof (int)); - сохраняется ли разница? Кроме того - это только для TCP? то есть для icmp / ping вы наблюдаете то же самое?
Кьетил Йоргенсен
1
Кроме того, есть ли разница в настройках объединения или разгрузки между «быстрым» и «медленным»? ethtool -c <dev> и ethtool -k <dev>. Стандартные драйверы могут измениться.
Кжетил Йоргенсен

Ответы:

1

Это не ответ, но важно тщательно калибровать задержки / пропускную способность. Это может помочь вам приблизиться к ответу и даже помочь другим здесь дать вам лучшие предложения по процессу, вызывающему корень.

Попробуйте получить более точные данные с помощью захвата wireshark / tshark на интерфейсе,

  1. Подтвердите, что пропускная способность фактически уменьшена вдвое и
  2. Определите, как распределяется задержка (между tx и rx)
    a. это равномерно по тесту?
    б. где-нибудь есть сосредоточенный ларек?
Nik
источник
0

Я просмотрел журналы изменений, возможно, это было введение QFQ

Журнал изменений сети Kernel 3.0 https://kernelnewbies.org/Linux_3.0#head-96d40fb6f9c48e789386dbe59fd5b5acc9a9059d

Страница коммиттера QFQ http://info.iet.unipi.it/~luigi/qfq/

Это обеспечивает жесткие гарантии обслуживания при чрезвычайно низкой стоимости за пакет.

m_krsic
источник