我想比较两个进程之间Unix域套接字的性能与另一个IPC的性能。
我有一个基本的程序,创建一个套接字对,然后调用fork。然后,它测量RTT以将8192字节发送到另一个进程并返回(每次迭代不同)。
#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <unistd.h>
int main(int argc, char **argv) {
int i, pid, sockpair[2];
char buf[8192];
struct timespec tp1, tp2;
assert(argc == 2);
// Create a socket pair using Unix domain sockets with reliable,
// in-order data transmission.
socketpair(AF_UNIX, SOCK_STREAM, 0, sockpair);
// We then fork to create a child process and then start the benchmark.
pid = fork();
if (pid == 0) { // This is the child process.
for (i = 0; i < atoi(argv[1]); i++) {
assert(recv(sockpair[1], buf, sizeof(buf), 0) > 0);
assert(send(sockpair[1], buf, sizeof(buf), 0) > 0);
}
} else { // This is the parent process.
for (i = 0; i < atoi(argv[1]); i++) {
memset(buf, i, sizeof(buf));
buf[sizeof(buf) - 1] = ' ';
assert(clock_gettime(CLOCK_REALTIME, &tp1) == 0);
assert(send(sockpair[0], buf, sizeof(buf), 0) > 0);
assert(recv(sockpair[0], buf, sizeof(buf), 0) > 0);
assert(clock_gettime(CLOCK_REALTIME, &tp2) == 0);
printf("%lu nsn", tp2.tv_nsec - tp1.tv_nsec);
}
}
return 0;
}
但是,我注意到对于每个重复测试,第一次运行的运行时间(I = 0)总是一个异常值:
79306 ns
18649 ns
19910 ns
19601 ns
...
我想知道内核是否必须在第一次调用send()
时做一些最终设置-例如,在内核中分配8192字节来缓冲调用send()
和recv()
之间的数据?
这不是数据复制需要额外的80微秒,这将是非常慢的(仅100 MB/s),这是你使用两个进程的事实,当父进程第一次发送数据时,这些数据需要等待子进程完成分叉并开始执行。
如果你绝对想使用两个进程,你应该首先在另一个方向上执行一个发送,这样父进程可以等待子进程准备好开始发送。
如:孩子:
send();
recv();
send();
父: recv();
gettime();
send();
recv();
gettime();
您还需要认识到,您的测试在很大程度上取决于进程在不同CPU核心上的位置,如果在同一核心上运行,将导致任务切换。
出于这个原因,我强烈建议您执行测量使用单个进程。即使没有民意调查,你也可以做到这一点如果你保持合理的小块适合套接字缓冲区的方法:
gettime();
send();
recv();
gettime();
您应该首先执行一个未测量的往返,以确保分配了缓冲区。
我猜想,所涉及的内核代码的指令缓存丢失是第一次执行时速度减慢的主要原因。也可能由于内核数据结构跟踪数据而导致数据缓存丢失。
延迟设置是可能的。
您可以通过在试验之间(包括在第一次试验之前)执行sleep(10)
来进行测试。做一些会使用所有CPU缓存的事情,比如在每次测试之间刷新网页。如果是惰性设置,那么第一次调用将会特别慢。如果不是,那么当缓存是冷的时候,所有的调用都会同样的慢。
在linux内核中,您可以找到send
使用的___sys_sendmsg
函数。检查此处查看代码。
buf
)从用户空间复制到内核空间。之后,recv
可以将接收到的消息从内核空间复制回子进程的用户空间。
这意味着对于send()recv()对,需要有2个memcpy和一个kmalloc。
第一个非常特殊,因为存储用户消息的空间没有分配。这也意味着也不存在于数据缓存中。所以第一个send() - recv()
对将分配内核内存来存储buf
,它也将被缓存。下面的调用将只使用函数原型中的used_address
参数使用该内存。
所以你的假设是正确的。第一次运行在内核中分配8KB并使用冷缓存,而其他运行只使用以前分配和缓存的数据。