【问题标题】:Measuring the latency of Unix domain sockets测量 Unix 域套接字的延迟
【发布时间】:2015-07-31 10:45:17
【问题描述】:

我想将两个进程之间的 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] = '\0';
            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() 之间的数据?

【问题讨论】:

  • 可能是这样,也可能与调度器中的时间有关。
  • 顺便说一句,在计算struct timespec 差异时,您可能不想忘记tv_sec。当第二个增加并且您将负纳秒差异格式化为 %lu 时,这将看起来像一个 数字。

标签: c performance sockets unix benchmarking


【解决方案1】:

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

如果你绝对要使用两个进程,你应该先向另一个方向执行一个发送,以便父级可以等待子级准备好之前 开始发送。

例如: 孩子:

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

家长:

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

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

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

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

您应该首先执行非测量往返以确保分配缓冲区。我很确定你会在这里得到更短的时间。

【讨论】:

    【解决方案2】:

    我猜所涉及的内核代码的指令缓存未命中是第一次通过时速度减慢的重要部分。可能还有用于跟踪内容的内核数据结构的数据缓存未命中。

    不过,延迟设置是可能的。

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

    【讨论】:

      【解决方案3】:

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

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

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

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

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

      【讨论】:

        猜你喜欢
        • 2013-06-24
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2015-12-15
        相关资源
        最近更新 更多