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 time
para 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;
}