Nel mio gruppo di ricerca, abbiamo recentemente aggiornato il sistema operativo sulle nostre macchine da Red Hat 6.2 a Debian 8.3 e osservato che il tempo di andata e ritorno TCP attraverso le schede di rete integrate Intel 1G tra le nostre macchine era raddoppiato da circa 110µs a 220µs.
All'inizio, ho pensato che fosse un problema di configurazione, quindi ho copiato tutte le configurazioni di sistema (come tcp_low_latency=1
) dalle macchine Red Hat non aggiornate alle macchine Debian e che non ha risolto il problema. Successivamente, ho pensato che potesse trattarsi di un problema di distribuzione Linux e ho installato Red Hat 7.2 sulle macchine, ma i tempi di andata e ritorno sono rimasti di circa 220µs.
Infine, ho pensato che forse il problema riguardava le versioni del kernel Linux da quando Debian 8.3 e Red Hat 7.2 avevano entrambi usato il kernel 3.x mentre Red Hat 6.2 ha usato il kernel 2.6. Quindi per provarlo, ho installato Debian 6.0 con kernel Linux 2.6 e bingo! I tempi erano di nuovo rapidi a 110µs.
Altri hanno anche sperimentato queste latenze più elevate nelle ultime versioni di Linux e ci sono soluzioni alternative conosciute?
Esempio minimo di lavoro
Di seguito è un'applicazione C ++ che può essere utilizzata per confrontare la latenza. Misura la latenza inviando un messaggio, aspettando una risposta e quindi inviando il messaggio successivo. Lo fa 100.000 volte con messaggi a 100 byte. Pertanto, possiamo dividere il tempo di esecuzione del client per 100.000 per ottenere le latenze di andata e ritorno. Per utilizzare questa prima compilare il programma:
g++ -o socketpingpong -O3 -std=c++0x Server.cpp
Quindi esegui la versione dell'applicazione lato server su un host (ad esempio 192.168.0.101). Specifichiamo l'IP per garantire che stiamo ospitando su un'interfaccia ben nota.
socketpingpong 192.168.0.101
Quindi utilizzare l'utilità Unix time
per misurare il tempo di esecuzione del client.
time socketpingpong 192.168.0.101 client
L'esecuzione di questo esperimento tra due host Debian 8.3 con hardware identico produce i seguenti risultati.
real 0m22.743s
user 0m0.124s
sys 0m1.992s
I risultati di Debian 6.0 sono
real 0m11.448s
user 0m0.716s
sys 0m0.312s
Codice:
#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;
}