简短的问题,但对我来说很难理解。
为什么 ePoll 的规模比 Poll 更好?
虽然 Damon 的原因对于您从不阻塞套接字的异常情况是正确的,但在典型的实际程序中,原因是完全不同的。一个典型的程序如下所示:
1)做我们现在能做的所有工作。
2)检查是否有任何网络连接需要服务,如果无事则阻塞。
3) 为发现的任何网络连接提供服务。
4) 转到步骤 1。
通常,因为您只是完成了所有可以做的工作,所以当您进入第 2 步时,您就没有工作要做了。所以你需要稍等片刻。现在,假设有 800 个您感兴趣的套接字。内核必须为这 800 个套接字中的每一个放入等待队列。而且,一瞬间,当数据到达这 800 个套接字中的一个时,内核必须将您从这 800 个等待队列中删除。将任务放在等待队列上需要创建一个“thunk”以将该任务链接到该等待队列。由于内核不知道您将等待哪个 800 个套接字,因此不可能进行良好的优化。
有了epoll
,epoll 套接字本身就有一个等待队列,并且进程只被放在一个等待队列中。需要一个 thunk 将 800 个连接中的每一个连接到 epoll 等待队列,但该 thunk 是持久的。您可以通过将套接字添加到 epoll 集中来创建它,并且它会一直保留在那里,直到您从集中删除套接字为止。
当套接字上有活动时,内核在检测活动的任务中处理它。当您等待时,内核已经知道是否检测到事件并且内核只需要将您放在那个等待队列中。当你醒来时,它只需要将你从那个队列中移除。
select
因此, or的杀手锏与其说是复制,不如poll
说是内核必须在每个阻塞操作上操纵大量等待队列。
poll 系统调用每次都需要将文件描述符列表复制到内核。这只会发生一次epoll_ctl
,但不是每次调用时都会发生epoll_wait
。
此外,epoll_wait
是O(1)
关于观看的描述符的数量1,这意味着您等待一个描述符还是等待 5,000 或 50,000 个描述符并不重要。poll
,虽然比 更有效select
,但每次仍然必须遍历列表(即,它是O(N)
关于描述符的数量)。
最后,除了“正常”模式之外,epoll 还可以在“边缘触发”模式下工作,这意味着内核不需要在您收到准备就绪信号后跟踪您读取了多少数据。这种模式更难掌握,但效率更高一些。
epoll_wait
当然仍然O(N)
是关于发生的事件。几乎没有任何方法可以与任何界面有所不同。如果N个事件发生在一个被监视的描述符上,那么应用程序需要获得N个通知,并且需要做N个“事情”才能对正在发生的事情做出反应。M
使用M <= N
. 在边沿触发模式下,当同一事件(例如,POLLIN
) 发生多次,您可能会收到更少的通知,可能只有一个。但是,这对于 big-O 符号本身并没有太大变化。
但是,epoll_wait
与观看的描述符数量无关。假设它以预期的“正常”方式使用(即,许多描述符,很少事件),这才是真正重要的,这里确实是O(1)
.
作为一个类比,您可以想到一个哈希表。哈希表在 中访问其内容O(1)
,但有人可能会争辩说计算哈希实际上O(N)
是与密钥长度有关。这在技术上是绝对正确的,并且可能存在这样的问题,但是,对于大多数人来说,这并不重要。