Linux select性能场景

3

我写了一个简单的TCP服务器应用程序,其中我的读取fd_set包括连接套接字描述符。当服务器应用程序收到消息时,它只是发送一个ACK。客户端应用程序只有在从服务器收到ACK后才会发送下一条消息。

// timeval == NULL
select(maxfd, &read_set, NULL, NULL, NULL)

当我这样做时,性能约为每秒3K条消息。发送确认和从客户端接收响应之间的延迟为0.3毫秒。

// tm.tv_sec=0 and tm.tv_usec=0
select(maxfd, &read_set, NULL, NULL, tm)

但如果我这样做,性能将达到8K条/秒,延迟降至0.18毫秒。

在后一种情况下,选择变成了轮询。有人能解释一下为什么后一种情况的表现比第一种情况要好得多吗?


如果你担心延迟,可以查看 epoll。至于它们的差异,是否一致?也许你可以阅读 libc/kernel 源代码,看看它们做了什么不同的事情。 - Some programmer dude
就像工程中的几乎所有事情一样,这是一个权衡。从系统角度来看,“select(...,NULL)”在几乎所有情况下都是明显的赢家:它能迅速响应;它不会占用系统资源。然而,在您的情况下,“select(...,tm = 0)”表现更好。为什么?1)因为您有一个连续的网络流量,2)响应网络是您关心的全部。 - paulsm4
3个回答

4

可能的答案

当超时时间为零时,如果没有数据可用,select()调用会立即返回。这使您可以忙碌等待轮询套接字,主动消耗CPU周期,直到数据到达。

当超时为NULL时,如果没有数据,您的进程将被置于WAIT_INTERRUPTIBLE状态下睡眠,等待数据变为可用。这至少会产生两个上下文切换的惩罚,一个是从您的进程转移开,另一个是当数据变得可用时重新回到它。这样做的好处是,您的进程放弃了CPU,允许其他进程运行。

这就像比较自旋锁和信号量。自旋锁“旋转”CPU等待条件,而信号量则放弃CPU。自旋锁的性能更高,但它们独占CPU,因此只应用于非常短暂的等待。信号量与其他进程更加合作,但由于额外的上下文切换而产生明显的开销。


上下文切换的成本只有几个纳秒。在我的设备上是66ns。考虑到每秒8k条消息,这并不累加。 - Jimm
@Jimm 你如何测量上下文切换时间?谷歌搜索显示其他人测量上下文切换时间在5-30微秒的范围内。 - John Kugelman
我使用了 https://github.com/tsuna/contextswitch/blob/master/timectxsw.c。你能分享给我其他链接吗?我可以尝试一下,看看有什么不同。 - Jimm
即使在30微秒内进行2次上下文切换,即60微秒,每次唤醒都应该额外增加60微秒的时间。在我的情况下,即使唤醒稍晚0.1-0.18毫秒。 - Jimm

1

这不会回答你的问题,但如果您真的想要良好的性能,并且接收消息的速率相当高,可以尝试以下方法:

  • 使用 O_NONBLOCK 打开
  • 首先尝试读取
  • 如果读取失败并出现 EAGAIN 或 EWOULDBLOCK 错误代码,请使用 timeval == NULL 进行选择
  • 处理数据

-2

select(2) 的 man 手册说:

timeout 是 select() 返回之前经过的时间上限。如果 timeval 结构的两个字段都为零,则 select() 立即返回。(这对轮询很有用。)如果 timeout 为 NULL(无超时),则 select() 可以无限期地阻塞

强调是我加的。如果您关心延迟,应该看看 epoll(4)


但是很可能read_set中的某些内容已经准备好了,所以这个“可以”并不能解释它。 - Ignacio Vazquez-Abrams
上限超时不应该有影响。只要服务器接收到消息,无论在上述两种情况下,选择操作都会返回。 - Jimm

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接