В моей исследовательской группе мы недавно обновили ОС на наших машинах с 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;
}