Dans mon groupe de recherche, nous avons récemment mis à niveau le système d'exploitation de nos machines de Red Hat 6.2 vers Debian 8.3 et observé que le temps d'aller-retour TCP via les cartes réseau Intel 1G intégrées entre nos machines avait doublé, passant d'environ 110 µs à 220 µs.
Au début, je pensais que c'était un problème de configuration, j'ai donc copié toutes les configurations sysctl (telles que tcp_low_latency=1
) des machines Red Hat non mises à niveau vers les machines Debian et cela n'a pas résolu le problème. Ensuite, j'ai pensé qu'il s'agissait peut-être d'un problème de distribution Linux et j'ai installé Red Hat 7.2 sur les machines, mais les temps d'aller-retour sont restés autour de 220µs.
Enfin, je me suis dit que le problème venait peut-être des versions du noyau Linux puisque Debian 8.3 et Red Hat 7.2 avaient tous deux utilisé le noyau 3.x tandis que Red Hat 6.2 utilisait le noyau 2.6. Donc, pour tester cela, j'ai installé Debian 6.0 avec le noyau Linux 2.6 et le bingo! Les temps étaient à nouveau rapides à 110µs.
D'autres ont-ils également connu ces latences plus élevées dans les dernières versions de Linux, et existe-t-il des solutions de contournement connues?
Exemple de travail minimum
Vous trouverez ci-dessous une application C ++ qui peut être utilisée pour comparer la latence. Il mesure la latence en envoyant un message, en attendant une réponse, puis en envoyant le message suivant. Il le fait 100 000 fois avec des messages de 100 octets. Ainsi, nous pouvons diviser le temps d'exécution du client par 100 000 pour obtenir les latences aller-retour. Pour utiliser cette première compilation du programme:
g++ -o socketpingpong -O3 -std=c++0x Server.cpp
Exécutez ensuite la version côté serveur de l'application sur un hôte (par exemple, 192.168.0.101). Nous spécifions l'IP pour nous assurer que nous hébergeons sur une interface bien connue.
socketpingpong 192.168.0.101
Et puis utilisez l'utilitaire Unix time
pour mesurer le temps d'exécution du client.
time socketpingpong 192.168.0.101 client
L'exécution de cette expérience entre deux hôtes Debian 8.3 avec un matériel identique donne les résultats suivants.
real 0m22.743s
user 0m0.124s
sys 0m1.992s
Les résultats de Debian 6.0 sont
real 0m11.448s
user 0m0.716s
sys 0m0.312s
Code:
#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;
}