测量 Unix 域套接字的延迟

Measuring the latency of Unix domain sockets

我想比较两个进程与另一个 IPC 之间的 Unix 域套接字的性能。

我有一个创建套接字对然后调用 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] = '[=10=]';
            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 ns\n", tp2.tv_nsec - tp1.tv_nsec);
        }
    }

    return 0;
}

但是,我注意到对于每次重复测试,第一个 运行 (i = 0) 的运行时间始终是异常值:

79306 ns
18649 ns
19910 ns
19601 ns
...

我想知道内核是否必须在第一次调用 send() 时进行一些最终设置 - 例如,在内核中分配 8192 字节来缓冲调用 send()recv()

我猜想所涉及的内核代码的指令缓存未命中是第一次通过时速度变慢的很大一部分。可能还有内核数据结构的数据缓存未命中,以跟踪内容。

虽然可以进行延迟设置。

您可以在试验之间(包括第一次试验之前)进行 sleep(10) 测试。在每次试验之间做一些会使用所有 CPU 缓存的事情,比如刷新网页。如果它是惰性设置,那么第一次调用会特别慢。如果不是,那么当缓存冷时所有调用都将同样缓慢。

在linux 内核中,您可以找到send 使用的___sys_sendmsg 函数。勾选here查看代码。

该函数必须将用户消息(在您的情况下为 8KB buf)从用户 space 复制到内核 space。之后recv可以从内核space将收到的消息复制回子进程的用户space。

这意味着你需要 2 个 memcpy 和一个 kmalloc 用于 send() recv()对.

第一个很特别,因为space没有分配存储用户消息的地方。这也意味着它也不存在于数据缓存中。所以第一个 send() - recv() 对将分配内核内存以存储 buf 并且也将被缓存。以下调用将仅使用函数原型中的 used_address 参数来使用该内存。

所以你的假设是正确的。第一个 运行 在内核中分配 8KB 并使用冷缓存,而其他只使用以前分配和缓存的数据。

不是数据复制需要额外 80 微秒,那会非常慢(仅 100 MB/s),这是您使用两个进程的事实,当 parent第一次发送数据,这些数据需要等待child完成fork并开始执行。

如果你绝对想使用两个进程,你应该先执行另一个方向的发送,这样 parent 可以等待 child 准备好 开始发送。

例如: Child:

  send();
  recv();
  send();

Parent:

  recv();
  gettime();
  send();
  recv();
  gettime();

您还需要意识到,您的测试在很大程度上取决于各种 CPU 内核上的进程放置,如果 运行 在同一内核上,将导致任务切换。

出于这个原因,我强烈建议您进行测量 使用单个进程。即使没有民意调查也没有任何东西,你可以这样做 前提是您保留适合套接字缓冲区的相当小的块:

gettime();
send();
recv();
gettime();

您应该首先执行 non-measured 往返以确保分配缓冲区。我很确定你在这里的时间会少得多。