根据我所阅读的和经历过的(基于Tornado的应用),我相信ePoll是选择和轮询网络的自然替代品,尤其是在Twisted中。这让我有点担心,更好的技术或方法很少不附带代价。
阅读了几十个epoll与其他替代方案之间的比较后可以发现,epoll在速度和可扩展性上显然是最佳选择,尤其是它具有线性扩展的特点,这非常出色。话虽如此,那处理器和内存利用率方面呢?epoll是否仍是最佳选择呢?
根据我所阅读的和经历过的(基于Tornado的应用),我相信ePoll是选择和轮询网络的自然替代品,尤其是在Twisted中。这让我有点担心,更好的技术或方法很少不附带代价。
阅读了几十个epoll与其他替代方案之间的比较后可以发现,epoll在速度和可扩展性上显然是最佳选择,尤其是它具有线性扩展的特点,这非常出色。话虽如此,那处理器和内存利用率方面呢?epoll是否仍是最佳选择呢?
对于非常少量的套接字(当然,这取决于您的硬件,但我们说的是大约10个或更少的数量),select
可以在内存使用和运行速度方面胜过 epoll
。 当然,对于这么少量的套接字,两种机制都非常快,所以在绝大多数情况下,您并不关心这种差异。
不过,需要澄清的是,select
和 epoll
都呈线性扩展。 不同之处在于,用户空间面向 API 的复杂性基于不同的内容。 在 select
调用的成本大致随着您传递给它的最高编号文件描述符的值而变化。 如果您选择单个 fd,100,则该成本大约是选择单个 fd,50 的两倍。将更低的 fd 添加到最高 fd 以下并非完全免费,因此在实践中要比这更复杂一些,但对于大多数实现来说,这是一个很好的第一近似。
epoll
的成本接近于实际上存在事件的文件描述符数。 如果您监视200个文件描述符,但只有其中的100个存在事件,则(非常粗略地)您只支付那100个活动文件描述符的成本。 这就是 epoll
倾向于在 select
上提供其主要优势之一的地方。 如果有一千个客户端大多处于空闲状态,那么当您使用 select
时,您仍然需要支付它们全部的成本。 然而,在使用 epoll
时,就像只有几个客户端- 您只支付每次活动的客户端成本。
epoll
会导致更少的CPU使用率。 至于内存使用情况,则有点难以抉择。select
确实设法以高度紧凑的方式表示所有必要信息(每个文件描述符一个比特)。而select
中FD_SETSIZE(通常为1024)对可以使用多少文件描述符的限制意味着你使用select
时永远不会超过每个fd集所需的128字节(读、写、异常)。相较于那384字节的最大值,epoll
有一点笨重。每个文件描述符由多字节结构表示。但是,就绝对值而言,它仍然不会使用太多内存。你可以在几十千字节内表示大量文件描述符(我认为每1000个文件描述符大约需要20k)。您还可以加入这样一个事实,即如果您只想监视一个文件描述符,但其值恰好为1024,则您必须花费select
的全部384字节,而对于epoll
,您只需花费20字节。尽管如此,这些数字都很小,因此并没有太大区别。另外,还有epoll
的另一个好处,也许您已经知道了,它不限于FD_SETSIZE
文件描述符。你可以使用它来监视你有的所有文件描述符。如果您只有一个文件描述符,但其值大于FD_SETSIZE
,epoll
也可以处理,但是select
不能。最近我偶然发现了 epoll
相对于 select
或者 poll
的一个小缺陷。虽然这三个 API 都不支持普通文件(即文件系统上的文件),但是 select
和 poll
将这种不支持表示为这些描述符一直可读且一直可写。这使它们无法用于任何有意义的非阻塞文件系统 I/O,即使用 select
或 poll
的程序,如果遇到来自文件系统的文件描述符,它至少会继续运行(或者如果失败,也不会是因为 select
或 poll
),尽管它可能不会具有最佳性能。
另一方面,当 epoll
被要求监视这样的文件描述符时,将会快速失败并显示错误(显然是 EPERM
)。严格来说,这几乎算不上错误。它只是以明确的方式表明了它不支持该文件描述符。正常情况下,我会赞扬明确的失败条件,但这个条件是未记录的(据我所知),导致应用程序完全崩溃,而不仅仅是可能性能下降的应用程序。
实际上,我只在与标准 I/O 交互时遇到过这个问题。用户可能会将 stdin 或 stdout 重定向到普通文件。此前,stdin 和 stdout 是一个管道(epoll
支持得很好),但现在它变成了普通文件,epoll
会大声失败并且导致应用程序崩溃。
select()
函数 - 除非您重新编译系统的一半以更改FD_SETSIZE的值 - 因此我想知道这种策略到底起作用了吗?对于您描述的情况,我可能会考虑使用poll()
函数,它更像select()
函数,而不像epoll()
函数,并且可以避免FD_SETSIZE的限制。 - Jean-Paul CalderoneFD_SETSIZE
是在编译 C 库时设置的编译时常量。如果在构建应用程序时将其定义为不同的值,则您的应用程序和 C 库将发生不一致,从而导致问题。如果您有参考资料声称重新定义 FD_SETSIZE
是安全的,我很想看看它们。 - Jean-Paul Calderone
poll
的行为? - quarkman select
中是否存在问题?Linux内核没有固定的限制,但glibc实现将fd_set定义为固定大小类型,FD_SETSIZE定义为1024,并且FD_*()宏根据该限制操作。要监视大于1023的文件描述符,请改用poll(2)。在CentOS 7上,我已经看到过自己的代码因为内核返回>1023的文件句柄而失败了select(),我目前正在研究一个类似的问题,这可能是Twisted遇到的同样问题。 - Paul D Smith