对write()进行双系统调用会导致大量网络速度下降

时间:2009-06-10 18:24:56

标签: network-programming

在我正在Linux上用C ++工作的部分分布式网络应用程序中,我有一个消息传递抽象,它将通过网络发送缓冲区。缓冲区分两步发送:首先发送一个包含大小的4字节整数,然后发送缓冲区。然后接收端也分两步接收 - 一次调用read()来获取大小,然后第二次调用读取有效载荷。因此,这涉及对read()的2次系统调用和对write()的2次系统调用。

在localhost上,我设置了两个测试进程。两个进程都在循环中连续发送和接收消息。每条消息的大小只有大约10个字节。出于某种原因,测试执行得非常慢 - 每秒发送/接收大约10条消息。这是在localhost上,甚至不是通过网络。

如果我更改代码以便只有一个系统调用写入,即发送进程将大小放在缓冲区的头部,然后只调用1次写入,整个过程就会大大加快 - 大约10000每秒发送/接收的消息。这是一个令人难以置信的速度差异,只需少一个系统调用即可。

对此有一些解释吗?

5 个答案:

答案 0 :(得分:1)

您可能会看到Nagle algorithm的效果,但我不确定它是否已为环回接口启用。

如果您可以将两个写入合并为一个,那么您应该始终这样做。如果你可以避免它,就没有必要承担多个系统调用的开销。

答案 1 :(得分:1)

好吧,我正在使用TCP / IP(SOCK_STREAM)套接字。示例代码非常简单。这是一个重现问题的基本代码段。这不包括所有锅炉板设置代码,错误检查或ntohs代码:

在发送端:

// Send size
uint32_t size = strlen(buffer);
int res = write(sock, &size, sizeof(size));

// Send payload
res = write(sock, buffer, size);

在接收端:

// Receive size
uint32_t size;
int res = read(sock, &size, sizeof(size));

// Receive payload
char* buffer = (char*) malloc(size);
read(sock, buffer, size);

基本上,如果我通过将大小打包到发送缓冲区来更改发送代码,并且只调用write(),则性能提高几乎快1000倍。

答案 2 :(得分:0)

这基本上是同一个问题:C# socket abnormal latency

简而言之,您将需要使用TCP_NODELAY套接字选项。您可以使用setsockopt设置它。

答案 3 :(得分:0)

您没有提供足够的信息来确定。你甚至没有说你正在使用哪种协议。

假设TCP / IP,套接字可以配置为在每次写入时发送数据包,而不是在内核中缓冲输出,直到缓冲区已满或明确刷新套接字。这意味着TCP以不同的片段发送两个数据,并且必须在另一端对其进行处理。

您可能也会看到TCP慢启动算法的效果。发送的第一个数据作为连接握手的一部分进行传输。然后,随着更多数据的传输,TCP窗口大小逐渐增加,直到它与接收器消耗数据的速率相匹配。这在长期连接中很有用,但在短期连接中却有很大的性能影响。您可以通过设置套接字选项来关闭慢启动。

查看TCP_NODELAY和TCP_NOPUSH套接字选项。

可以用来避免多个系统调用和碎片的优化是分散/收集I / O.使用sendv或writev系统调用,您可以在单个系统调用中发送4字节大小和可变大小的缓冲区,并且这两个数据将通过TCP在同一片段中发送。

答案 4 :(得分:0)

问题在于,第一次调用send时,系统不知道第二次调用即将到来,因此它会立即发送数据。通过第二次调用send,系统不知道第三个调用没有到来,因此它会延迟数据,希望它可以将数据与后续调用结合起来。

正确的解决方法是使用“聚集”操作,例如writev,如果您的操作系统支持它。否则,分配缓冲区,复制两个块,然后只调用write。 (某些操作系统有其他解决方案,例如Linux具有“TCP软木塞”操作。)

它并不重要,但您也应优化接收代码。调用'read'请求尽可能多的字节,然后自己解析它们。你想要将操作系统教给你的协议,这不是一个好主意。