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 TCP através das placas de rede integradas Intel 1G entre nossas máquinas dobrou de 110µs para 220µs.
Inicialmente, achei que era um problema de configuração, então copiei todas as configurações de 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.
Por último, imaginei que talvez o problema estivesse nas versões do kernel do Linux, uma vez que o Debian 8.3 e o Red Hat 7.2 usaram 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 Linux 2.6 e o bingo! Os tempos foram rápidos novamente em 110µs.
Outras pessoas também experimentaram essas latências mais altas nas versões mais recentes do Linux e existem soluções conhecidas?
Exemplo Mínimo de Trabalho
Abaixo está um aplicativo C ++ que pode ser usado para avaliar a latência. Ele mede a latência enviando uma mensagem, aguardando uma resposta e enviando a próxima mensagem. Ele faz isso 100.000 vezes com mensagens de 100 bytes. Assim, podemos dividir o tempo de execução do cliente em 100.000 para obter as latências de ida e volta. Para usar este primeiro compilar 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 (digamos em 192.168.0.101). Nós especificamos o IP para garantir que estamos hospedando em uma interface bem conhecida.
socketpingpong 192.168.0.101
E, em seguida, use o utilitário Unix time
para medir o tempo de execução do cliente.
time socketpingpong 192.168.0.101 client
A execução desta experiência 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;
}