一种在 C 中非常有效(快速)的网络轮询的方法
A method for very efficient (fast) network polling in C
精简版: 我不想使用第三方库或框架,如 Netmap 或 DPDK,有没有比 poll()
或 [=13= 更快的东西],或者我可以提高这些调用的效率吗?
完整版:我有一个单线程应用程序,它使用单个套接字尽可能快地发送数据,我正在使用 valgrind/cachegrind/callgrind 来尝试增加效率(以及应用程序的吞吐量)。
目前,接收主机站点处于无限循环中,试图尽快检查接收到的数据(这需要是非阻塞的,当没有数据包要处理时 "other stuff" 已完成)。我在接收主机上使用 select()
,因为 select()
使用提供微秒轮询频率的时间间隔,而 poll()
使用毫秒值。 Callgrind 向我展示 FD_SET()
执行的指令几乎是 select()
操作的两倍,select()
的一个缺点是我必须 运行 FD_SET()
接收循环的每次迭代:
. . . . . . . . . // Poll for incoming frames
4,361,236 1 1 2,180,618 0 0 2,180,618 0 0 TEST_INTERFACE->TV_SELECT_DELAY.tv_sec = 0;
4,361,236 0 0 2,180,618 0 0 2,180,618 0 0 TEST_INTERFACE->TV_SELECT_DELAY.tv_usec = 000000;
56,696,068 2 2 15,264,326 0 0 2,180,618 0 0 FD_SET(TEST_INTERFACE->SOCKET_FD, &TEST_INTERFACE->FD_READS);
. . . . . . . . .
. . . . . . . . . TEST_INTERFACE->SELECT_RET_VAL = select(TEST_INTERFACE->SOCKET_FD_COUNT,
. . . . . . . . . &TEST_INTERFACE->FD_READS,
. . . . . . . . . NULL, NULL,
30,528,652 1 1 10,903,090 0 0 15,264,326 0 0 &TEST_INTERFACE->TV_SELECT_DELAY);
. . . . . . . . .
. . . . . . . . .
9,432,052 0 0 4,361,236 0 0 0 0 0 if (TEST_INTERFACE->SELECT_RET_VAL > 0 &&
7,450,590 1 1 2,128,740 0 0 0 0 0 FD_ISSET(TEST_INTERFACE->SOCKET_FD, &TEST_INTERFACE->FD_READS))
. . . . . . . . . {
. . . . . . . . .
. . . . . . . . . RX_LEN = recvfrom(TEST_INTERFACE->SOCKET_FD,
. . . . . . . . . FRAME_HEADERS->RX_BUFFER,
. . . . . . . . . TEST_PARAMS->F_SIZE_TOTAL,
5,321,850 1 1 2,128,740 0 0 2,838,320 0 0 0, NULL, NULL);
. . . . . . . . .
我使用 select()
获得平均 130-140Mpbs 的接收吞吐量。使用 poll()
我得到平均 150-160Mbps。
. . . . . . . . . // Poll for incoming frames
7,347,032 2 2 1,836,758 0 0 4,591,895 0 0 TEST_INTERFACE->SELECT_RET_VAL = poll(TEST_INTERFACE->fds, 1, 0);
. . . . . . . . .
. . . . . . . . .
3,673,516 0 0 1,836,758 0 0 0 0 0 if (TEST_INTERFACE->SELECT_RET_VAL > 0)
. . . . . . . . . {
. . . . . . . . .
2,142,186 0 0 714,062 0 0 0 0 0 if ( TEST_INTERFACE->fds[0].revents & POLLIN )
714,062 0 0 357,031 0 0 357,031 0 0 TEST_INTERFACE->fds[0].revents = 0;
. . . . . . . . .
. . . . . . . . . RX_LEN = recvfrom(TEST_INTERFACE->SOCKET_FD,
. . . . . . . . . FRAME_HEADERS->RX_BUFFER,
. . . . . . . . . TEST_PARAMS->F_SIZE_TOTAL,
5,355,465 1 1 2,142,186 0 0 2,856,248 0 0 0, NULL, NULL);
使用 poll()
我传递了 0
的超时值所以希望它在任何时候都不会阻塞,但我不确定这是否正确并且它实际上比select()
.
在实验室中,我有几台带有 10Gbps NIC 的背对背服务器。使用 select()
和 Tx 主机以 10Gbps 的线路速率发送数据,Rx 主机可以处理大约 9.5Gbps 的流量。上面的结果只是在我的笔记本电脑上的虚拟机之间(实验室目前停止运行)以表明 poll()
使我的 Rx 主机略有增加。在上面的结果中,我们可以看到 poll()
更快,但是 10Gbps 是每几十纳秒一个数据包,所以我不确定(当实验室再次工作时)我是否会看到任何改进。
所以我想我有两个问题:
在我的 poll()
与 select()
测试中,因为我使用的是单个套接字 poll()
稍快一些,但是这是否适用于更快的连接,10 /20/30/40Gbps,或者以这些速度 select()
会更快吗?
因为我只使用一个插座,如果我从select()
/poll()
切换到epoll()
,速度会提高吗?或者我可以使用其他一些“内置”方法,它比 poll()
和 select()
更快,或者我可以做些什么来减少它们的执行时间?
如果 select 的 fd(s) 不改变,您可以(通常)作弊并只使用预先计算的 int。
int fds = 1 << fd;
int fdp;
do {
fdp = fds;
n = select(fd+1, &fdp, ...
如果 FD_SET 是瓶颈,这应该会有所帮助。
select 的低效之处之一是复制内核 space 的大 fd_sets in/out——确保 fd 足够低以适合 int 有帮助。
我假设线程已关闭 table。
您还可以通过使用非阻塞套接字获得一些帮助,并且只在它说 EAGAIN 时调用 poll/select(并且您有 none 个 "other work" 要做)。
精简版: 我不想使用第三方库或框架,如 Netmap 或 DPDK,有没有比 poll()
或 [=13= 更快的东西],或者我可以提高这些调用的效率吗?
完整版:我有一个单线程应用程序,它使用单个套接字尽可能快地发送数据,我正在使用 valgrind/cachegrind/callgrind 来尝试增加效率(以及应用程序的吞吐量)。
目前,接收主机站点处于无限循环中,试图尽快检查接收到的数据(这需要是非阻塞的,当没有数据包要处理时 "other stuff" 已完成)。我在接收主机上使用 select()
,因为 select()
使用提供微秒轮询频率的时间间隔,而 poll()
使用毫秒值。 Callgrind 向我展示 FD_SET()
执行的指令几乎是 select()
操作的两倍,select()
的一个缺点是我必须 运行 FD_SET()
接收循环的每次迭代:
. . . . . . . . . // Poll for incoming frames
4,361,236 1 1 2,180,618 0 0 2,180,618 0 0 TEST_INTERFACE->TV_SELECT_DELAY.tv_sec = 0;
4,361,236 0 0 2,180,618 0 0 2,180,618 0 0 TEST_INTERFACE->TV_SELECT_DELAY.tv_usec = 000000;
56,696,068 2 2 15,264,326 0 0 2,180,618 0 0 FD_SET(TEST_INTERFACE->SOCKET_FD, &TEST_INTERFACE->FD_READS);
. . . . . . . . .
. . . . . . . . . TEST_INTERFACE->SELECT_RET_VAL = select(TEST_INTERFACE->SOCKET_FD_COUNT,
. . . . . . . . . &TEST_INTERFACE->FD_READS,
. . . . . . . . . NULL, NULL,
30,528,652 1 1 10,903,090 0 0 15,264,326 0 0 &TEST_INTERFACE->TV_SELECT_DELAY);
. . . . . . . . .
. . . . . . . . .
9,432,052 0 0 4,361,236 0 0 0 0 0 if (TEST_INTERFACE->SELECT_RET_VAL > 0 &&
7,450,590 1 1 2,128,740 0 0 0 0 0 FD_ISSET(TEST_INTERFACE->SOCKET_FD, &TEST_INTERFACE->FD_READS))
. . . . . . . . . {
. . . . . . . . .
. . . . . . . . . RX_LEN = recvfrom(TEST_INTERFACE->SOCKET_FD,
. . . . . . . . . FRAME_HEADERS->RX_BUFFER,
. . . . . . . . . TEST_PARAMS->F_SIZE_TOTAL,
5,321,850 1 1 2,128,740 0 0 2,838,320 0 0 0, NULL, NULL);
. . . . . . . . .
我使用 select()
获得平均 130-140Mpbs 的接收吞吐量。使用 poll()
我得到平均 150-160Mbps。
. . . . . . . . . // Poll for incoming frames
7,347,032 2 2 1,836,758 0 0 4,591,895 0 0 TEST_INTERFACE->SELECT_RET_VAL = poll(TEST_INTERFACE->fds, 1, 0);
. . . . . . . . .
. . . . . . . . .
3,673,516 0 0 1,836,758 0 0 0 0 0 if (TEST_INTERFACE->SELECT_RET_VAL > 0)
. . . . . . . . . {
. . . . . . . . .
2,142,186 0 0 714,062 0 0 0 0 0 if ( TEST_INTERFACE->fds[0].revents & POLLIN )
714,062 0 0 357,031 0 0 357,031 0 0 TEST_INTERFACE->fds[0].revents = 0;
. . . . . . . . .
. . . . . . . . . RX_LEN = recvfrom(TEST_INTERFACE->SOCKET_FD,
. . . . . . . . . FRAME_HEADERS->RX_BUFFER,
. . . . . . . . . TEST_PARAMS->F_SIZE_TOTAL,
5,355,465 1 1 2,142,186 0 0 2,856,248 0 0 0, NULL, NULL);
使用 poll()
我传递了 0
的超时值所以希望它在任何时候都不会阻塞,但我不确定这是否正确并且它实际上比select()
.
在实验室中,我有几台带有 10Gbps NIC 的背对背服务器。使用 select()
和 Tx 主机以 10Gbps 的线路速率发送数据,Rx 主机可以处理大约 9.5Gbps 的流量。上面的结果只是在我的笔记本电脑上的虚拟机之间(实验室目前停止运行)以表明 poll()
使我的 Rx 主机略有增加。在上面的结果中,我们可以看到 poll()
更快,但是 10Gbps 是每几十纳秒一个数据包,所以我不确定(当实验室再次工作时)我是否会看到任何改进。
所以我想我有两个问题:
在我的
poll()
与select()
测试中,因为我使用的是单个套接字poll()
稍快一些,但是这是否适用于更快的连接,10 /20/30/40Gbps,或者以这些速度select()
会更快吗?因为我只使用一个插座,如果我从
select()
/poll()
切换到epoll()
,速度会提高吗?或者我可以使用其他一些“内置”方法,它比poll()
和select()
更快,或者我可以做些什么来减少它们的执行时间?
如果 select 的 fd(s) 不改变,您可以(通常)作弊并只使用预先计算的 int。
int fds = 1 << fd;
int fdp;
do {
fdp = fds;
n = select(fd+1, &fdp, ...
如果 FD_SET 是瓶颈,这应该会有所帮助。
select 的低效之处之一是复制内核 space 的大 fd_sets in/out——确保 fd 足够低以适合 int 有帮助。
我假设线程已关闭 table。
您还可以通过使用非阻塞套接字获得一些帮助,并且只在它说 EAGAIN 时调用 poll/select(并且您有 none 个 "other work" 要做)。