一种在 C 语言中非常高效(快速)网络轮询的方法



简短版本:我不想使用第三方库或帧,如Netmap或DPDK,有没有比poll()select()更快的东西,或者我可以提高这些调用的效率吗?

完整版:我有一个单线程应用程序,它使用单个套接字尽可能快地发送数据,我正在使用 valgrind/cachegrind/callgrind 来尝试提高效率(从而提高应用程序的吞吐量(。

目前,接收

主机站点处于无限循环中,试图尽快检查接收的数据(这需要是非阻塞的,当没有数据包来处理"其他东西"时(。我在接收主机上使用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()与以10Gbps线速发送数据的Tx主机,Rx主机可以处理大约9.5Gbps的流量。以上结果仅介于我笔记本电脑上的虚拟机之间(实验室目前已停止运行(,以表明poll()在 Rx 主机上略有增加。在上面的结果中,我们可以看到poll()更快,但是 10Gbps 是每隔几十纳秒一个数据包,所以我不确定(当实验室再次工作时(我是否会看到任何改进。

所以我想我有两个问题:

  1. 在我的poll()select()测试中,由于我使用的是单插槽poll()速度稍快,但这是否适用于更快的连接,10/20/30/40Gbps,或者在这些速度下select()会更快?

  2. 由于我只使用一个插座,如果我从 select()/poll()切换到 epoll(),速度是否会提高?或者我可以使用其他一些"内置"方法,比poll()select()更快,或者我可以做些什么来减少它们的执行时间?

如果要选择的 fd(s( 没有改变,你可以(通常(作弊,只使用预先计算的 int。

int   fds = 1 << fd;
int   fdp;
 do {
     fdp = fds;
     n = select(fd+1, &fdp, ...

如果FD_SET是瓶颈,这应该对一些人有所帮助。

select 的低效率之一是将大fd_sets复制到/复制出内核空间 - 确保 fd 足够低以适应 int 帮助。


我将假设线程不在桌面上。

您还可以通过使用非阻塞套接字以及仅在显示 EAGAIN 时才调用 poll/select 来获得一些帮助(并且您没有"其他工作"要做(。

相关内容

  • 没有找到相关文章

最新更新