【问题标题】:Caveats of select/poll vs. epoll reactors in TwistedTwisted 中 select/poll 与 epoll 反应器的注意事项
【发布时间】:2011-01-03 04:57:38
【问题描述】:

我阅读和体验过的所有内容(基于 Tornado 的应用程序)都让我相信 ePoll 是基于 Select 和 Poll 的网络的自然替代品,尤其是 Twisted。这让我产生了偏执,很少有更好的技术或方法不付出代价。

阅读 epoll 和替代方案之间的几十个比较表明,epoll 显然是速度和可扩展性的冠军,特别是它以线性方式扩展,这太棒了。也就是说,处理器和内存利用率如何,epoll 仍然是冠军吗?

【问题讨论】:

    标签: networking scalability twisted capacity-planning


    【解决方案1】:

    对于非常少量的套接字(当然,取决于您的硬件,但我们谈论的是大约 10 个或更少的套接字),select 可以在内存使用和运行速度方面击败 epoll。当然,对于如此少量的套接字,这两种机制都非常快,以至于在绝大多数情况下您并不真正关心这种差异。

    不过,有一个澄清。 select 和 epoll 都是线性缩放的。然而,一个很大的不同是,面向用户空间的 API 具有基于不同事物的复杂性。 select 调用的成本大致与您传递给它的编号最高的文件描述符的值有关。如果您在单个 fd 上选择 100,那么这大约是在单个 fd 上选择 50 的两倍。在最高值以下添加更多 fd 并不是完全免费的,因此在实践中它比这更复杂一些,但是这个对于大多数实现来说是一个很好的初步近似值。

    epoll 的开销更接近于实际在其上具有事件的文件描述符的数量。如果您正在监视 200 个文件描述符,但其中只有 100 个有事件,那么您(非常粗略地)只需为这 100 个活动文件描述符付费。这就是 epoll 倾向于提供其优于 select 的主要优势之一的地方。如果您有一千个大部分空闲的客户,那么当您使用 select 时,您仍然需要为全部一千个客户付费。但是,使用 epoll,就好像您只有几个 - 您只需为在任何给定时间处于活动状态的那些付费。

    所有这一切都意味着 epoll 将减少大多数工作负载的 CPU 使用率。就内存使用而言,这有点折腾。 select 确实设法以高度紧凑的方式(每个文件描述符一位)表示所有必要的信息。 FD_SETSIZE(通常为 1024)限制您可以与 select 一起使用多少个文​​件描述符,这意味着您永远不会为您可以与 select 一起使用的三个 fd 集(读、写、例外)。与最大 384 字节相比,epoll 有点像猪。每个文件描述符都由一个多字节结构表示。但是,绝对而言,它仍然不会使用太多内存。您可以在几十 KB 中表示大量文件描述符(我认为每 1000 个文件描述符大约 20k)。如果您只想监视一个文件描述符但它的值恰好是 1024,那么您还可以考虑这样一个事实,即您必须将所有 384 个字节用于select,而使用 epoll 您只需花费 20 个字节。尽管如此,所有这些数字都非常小,所以没有太大区别。

    还有 epoll 的其他好处,也许您已经知道,它不限于 FD_SETSIZE 文件描述符。您可以使用它来监控尽可能多的文件描述符。如果你只有一个文件描述符,但它的值大于 FD_SETSIZE,epoll 也可以使用,但select 不行。

    随机地,我最近还发现了 epollselectpoll 相比的一个小缺点。虽然这三个 API 都不支持普通文件(即文件系统上的文件),但selectpoll 表示缺乏支持,因为报告此类描述符始终可读且始终可写。这使得它们不适合任何有意义的非阻塞文件系统 I/O,使用 selectpoll 并且碰巧遇到来自文件系统的文件描述符的程序至少会继续运行(或者如果它失败,不会是因为selectpoll),尽管它可能不是最好的性能。

    另一方面,epoll 在被要求监视此类文件描述符时会快速失败并出现错误(显然是EPERM)。严格来说,这几乎是不正确的。它只是以明确的方式表明它缺乏支持。通常我会为明确的失败条件鼓掌,但这种情况没有记录(据我所知)并导致应用程序完全崩溃,而不是仅仅以潜在的性能下降运行。

    在实践中,我见过的唯一出现这种情况的地方是与 stdio 交互时。用户可能会将标准输入或标准输出从/重定向到普通文件。而以前的标准输入和标准输出本来是一个管道——epoll 支持得很好——然后它变成了一个普通文件,epoll 大声失败,破坏了应用程序。

    【讨论】:

    • 非常好的答案。考虑明确说明 poll 的行为以确保完整性?
    • 我对从普通文件读取行为的两分钱:我通常更喜欢彻底失败而不是性能下降。原因是它更有可能在开发过程中被检测到,因此可以正常工作(比如通过另一种方法对实际文件进行 I/O)。当然是 YMMV:可能不会出现明显的减速,在这种情况下失败也不是更好。但是,仅在特殊情况下才发生的显着减速在开发过程中很难捕捉到,在实际部署时会成为定时炸弹。
    • 刚刚完全阅读了您的编辑。从某种意义上说,我确实同意 epoll 不模仿它的前辈可能是不正确的,但我可以再次想象实现 EPERM 错误的开发人员认为“仅仅因为它总是被破坏,并不能正确地破坏我的好吧。”还有另一个反驳论点,我是一个防御性程序员,任何超过 1+1 的事情都是可疑的,我以允许优雅失败的方式编码。让内核触发一个超出预期的错误既不好也不体贴。
    • @Jean-Paul 你能补充一些关于 kqueue 的解释吗?
    • 抛开性能不谈,这是否会导致问题(来自man select) Linux 内核没有施加固定限制,但 glibc 实现使 fd_set 成为固定大小类型,其中 FD_SETSIZE 定义为 1024 ,以及根据该限制运行的 FD_*() 宏。要监视大于 1023 的文件描述符,请改用 poll(2)。在 CentOS 7 上,我已经看到我自己的代码在 select() 中失败的问题,因为内核返回了一个 >1023 的文件句柄,而我目前正在寻找一个问题,闻起来可能是 Twisted 遇到了同样的问题。
    【解决方案2】:

    在我公司的测试中,出现了 epoll() 的一个问题,因此与 select 相比,成本是单一的。

    当尝试从网络中读取超时时,创建一个 epoll_fd(而不是 FD_SET)并将 fd 添加到 epoll_fd,比创建一个 FD_SET(它是一个简单的 malloc)要昂贵得多。

    根据前面的回答,随着进程中FD的数量变大,select()的代价会越来越高,但是在我们的测试中,即使fd值在10000个,select仍然是赢家。在这些情况下,线程只有一个 fd 正在等待,并且只是试图克服网络读取和网络写入在使用阻塞线程模型时不会超时的事实。当然,与非阻塞反应器系统相比,阻塞线程模型的性能较低,但在某些情况下,为了与特定的遗留代码库集成,它是必需的。

    这种用例在高性能应用程序中很少见,因为反应器模型不需要每次都创建一个新的 epoll_fd。对于 epoll_fd 长期存在的模型——这显然是任何高性能服务器设计的首选——epoll 在各方面都是明显的赢家。

    【讨论】:

    • 但是如果你的文件描述符值在 10k+ 范围内,你甚至不能使用select() - 除非你重新编译一半系统以更改 FD_SETSIZE - 所以我想知道这个策略是如何工作的。对于您描述的场景,我可能会查看poll(),它更像select(),而不是epoll() - 但消除了FD_SETSIZE 限制。
    • 如果文件描述符值在 10K 范围内,则可以使用 select(),因为可以 malloc() FD_SET。事实上,由于 FD_SETSIZE 是编译时并且实际的 fd 限制是在运行时,FD_SET 的唯一安全使用会根据 FD_SET 的大小检查文件描述符的数量,如果 FD_SET 是,则执行 malloc(或道德等价)太小。当我与客户一起在生产中看到这一点时,我感到震惊。在编写套接字 20 年后,我曾经编写的所有代码 - 以及网络上的大多数教程 - 都是不安全的。
    • 据我所知,在任何流行的平台上都是不正确的。 FD_SETSIZE 是编译 C 库时设置的编译时间常数。如果您在构建应用程序时将其定义为不同的值,那么您的应用程序和 C 库将不同意,事情会变得很糟糕。如果您有引用声称重新定义 FD_SETSIZE 是安全的,我很想看到它们。
    猜你喜欢
    • 2011-05-01
    • 1970-01-01
    • 2013-01-25
    • 1970-01-01
    • 2013-08-03
    • 1970-01-01
    • 2010-09-18
    • 2011-05-04
    • 2012-01-31
    相关资源
    最近更新 更多