测量Unix域套接字的延迟

时间:2015-07-31 10:45:18

标签: c performance sockets unix benchmarking

我想将两个进程之间的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] = '\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()和{{}之间缓冲数据1}?

3 个答案:

答案 0 :(得分:1)

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

但是,懒惰的设置是可能的。

您可以在试验之间(包括在第一次试验之前)进行sleep(10)测试。在每次试用之间执行一些可以使用所有CPU缓存的内容,例如刷新网页。如果它是懒惰的设置,那么第一次调用会非常慢。如果没有,那么当缓存很冷时,所有呼叫都会同样缓慢。

答案 1 :(得分:1)

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

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

这意味着您需要 2 memcpy kmalloc send() recv()

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

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

答案 2 :(得分:0)

这不是需要80多微秒的数据副本,这将非常慢(仅100 MB / s),这是因为您正在使用两个进程,而且当父进程首次发送数据,这些数据需要等待子进程完成fork并开始执行。

如果您绝对想要使用两个进程,则应首先在另一个方向执行发送,以便父进程可以等待子进程准备好 开始发送。

例如: 子:

<!DOCTYPE html>
<html>
<head>
     <script src="dist/handsontable.full.js"></script>
     <link rel="stylesheet" media="screen" href="dist/handsontable.full.css">
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.3/jquery.min.js"></script>

<script>
    var data = [
    ["", "Ford", "Volvo", "Toyota", "Honda"],
    ["2014", 10, 11, 12, 13],
    ["2015", 20, 11, 14, 13],
    ["2016", 30, 15, 12, 13]
    ];

  var container = document.getElementById('example');
  var hot = new Handsontable(container, {
            data: data,
            minSpareRows: 1,
            rowHeaders: true,
            colHeaders: true,
            contextMenu: true
          });

</script>
<style>
body {
background-color: white;
margin: 20px;
}

h2 {
  margin: 20px 0;
}
</style>
</head>

 <body>


    <h2>Default Handsontable Demo</h2>

     <div id="example"></div>

 </body>

父:

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

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

出于这个原因,我强烈建议您进行测量 使用单个过程。即使没有民意调查也没有,你可以这样做 如果您保留适合插座缓冲区的合理小块:

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

首先应执行非测量往返,以确保分配缓冲区。我很确定你在这里会变得更小。

相关问题