Twisted中Select / Poll与Epoll反应器的警告


95

我已经阅读和体验过的所有内容(基于Tornado的应用程序)使我相信ePoll可以自然替代基于Select和Poll的网络,尤其是Twisted。这让我感到偏执,对于一个更好的技术或方法学来说,这是很罕见的,而没有价格。

阅读epoll与替代方案之间的几十个比较结果,可以看出epoll显然是速度和可伸缩性的拥护者,特别是它以线性方式扩展,这太棒了。也就是说,关于处理器和内存利用率,epoll仍然是冠军吗?

Answers:


190

对于极少数的套接字(当然,取决于您的硬件,它会有所不同,但是我们谈论的是10个或更少的数量级),select可以在内存使用率和运行速度上击败epoll。当然,对于这么少的套接字,这两种机制都非常快,以至于您在大多数情况下都不关心这种差异。

不过,需要澄清一下。选择和epoll均线性缩放。但是,最大的区别是面向用户空间的API具有基于不同事物的复杂性。select调用的费用与传递给它的编号最高的文件描述符的值大致相同。如果选择一个单一的fd(100),则大约是选择一个单一的fd(50)的代价的两倍。在最高的fd之下添加更多的fd并不是完全免费的,因此实际上比这要复杂一些。对于大多数实现而言,它是一个很好的第一近似值。

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

所有这些意味着epoll将导致大多数工作负载的CPU使用率降低。就内存使用而言,这有点麻烦。 select确实以高度紧凑的方式(每个文件描述符一位)来表示所有必要的信息。FD_SETSIZE(通常为1024)限制了可以使用的文件描述符的数量,这select意味着您可以使用的三个fd集中的每一个都不会花费超过128个字节select(读,写,异常)。与最大384个字节相比,epoll有点像猪。每个文件描述符都由一个多字节结构表示。但是,绝对而言,它不会占用太多内存。您可以在几十个千字节中表示大量的文件描述符(我认为每1000个文件描述符大约20k)。而且,select如果您只想监视一个文件描述符,但它的值恰好是1024,则必须花费所有384个字节,而使用epoll则仅花费20个字节。尽管如此,所有这些数字都很小,所以并没有太大的区别。

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

随机地,我最近还发现epollselect或相比有一个轻微的缺点poll。尽管这三个API均不支持普通文件(即文件系统上的文件),但由于将此类描述符报告为始终可读且始终可写,select因此poll存在这种缺乏支持。这使得它们不适合任何有意义的非阻塞文件系统I / O,使用selectpoll碰巧从文件系统遇到文件描述符的程序至少会继续运行(或者,如果失败,那不是因为的selectpoll),尽管它也许不是最佳的性能。

另一方面,当要求监视此类文件描述符时,epoll它将快速失败并显示错误(EPERM)。严格来说,这几乎是不正确的。它只是以明确的方式表明其缺乏支持。通常,我会为显式的故障情况表示赞赏,但是这种情况是无证的(据我所知),会导致应用程序完全崩溃,而不是仅仅以可能降低性能的方式运行。

在实践中,我唯一看到的地方是与stdio交互时。用户可以将stdin或stdout从/重定向到普通文件。以前的stdin和stdout可能是一个管道(由epoll支持就很好了),然后它变成了普通文件,epoll大声失败,从而破坏了应用程序。


很好的答案。考虑对行为明确poll表示完整性吗?
夸克2010年

6
我的两分钱是关于读取普通文件的行为:我通常宁愿完全失败也不愿降低性能。原因是它很可能在开发过程中被检测到,因此可以正常工作(例如,通过使用另一种对实际文件进行I / O的方法)。YMMV当然:在这种情况下,故障不会更好,但速度可能不会明显下降。但是仅在特殊情况下发生的急剧减速可能很难在开发过程中发现,而在实际部署时却成为定时炸弹。
夸克2010年

1
只需完全阅读您的编辑即可。从某种意义上说,我确实同意,epoll不模仿其前辈可能是不正确的,但是我可以再次想象实现EPERM错误的开发人员认为:“只是因为它总是被破坏,所以没有权利像我一样破坏我的东西。好。” 还有另一个反驳,我是一名防御性程序员,怀疑超出1 + 1的任何事物,并且我以允许出现正常失败的方式进行编码。让内核启动意外错误不是很好,也不是体贴的。
大卫2010年

1
@ Jean-Paul您还可以添加有关kqueue的一些解释吗?
好人

除了性能,man selectLinux内核没有施加固定的限制,但是glibc实现使fd_set为固定大小的类型,其中FD_SETSIZE定义为1024,而FD _ *()宏根据这个限制。要监视大于1023的文件描述符,请改用poll(2)。在CentOS 7上,我已经遇到了我自己的代码未能通过select()失败的问题,因为内核返回的文件句柄> 1023,而我目前正在查看一个闻起来好像是Twisted命中同一问题的问题。
Paul D Smith

4

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

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

根据前面的答案,随着过程中FD数量的增加,select()的成本会增加,但是在我们的测试中,即使fd值为10,000,select仍然是赢家。在这些情况下,只有一个fd在等待线程,并且只是试图克服使用阻塞线程模型时网络读取和网络写入不会超时的事实。当然,与非阻塞反应堆系统相比,阻塞线程模型的性能较低,但是在某些情况下,需要与特定的传统代码库集成。

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


5
但是,select()如果文件描述符的值在10k +范围内,您甚至无法使用-除非您重新编译一半的系统以更改FD_SETSIZE-所以我想知道这种策略是如何工作的。对于您所描述的场景,我可能会看看poll()哪一个select()比它看起来更像epoll()-但删除了FD_SETSIZE限制。
Jean-Paul Calderone 2014年

如果文件描述符值在10K范围内,则可以使用select(),因为可以malloc()FD_SET。实际上,由于FD_SETSIZE是编译时且实际fd限制是在运行时,因此,仅安全使用FD_SET会根据FD_SET的大小检查文件描述符的数量,如果FD_SET为太小。当我在与客户的生产中看到这一点时,我感到非常震惊。在对套接字进行了20年的编程之后,我曾经编写的所有代码以及网络上的大多数教程都是不安全的。
Brian Bulkowski

5
据我所知,在任何流行的平台上都不是这样。 FD_SETSIZE是在编译C库时设置的编译时间常数。如果在构建应用程序时将其定义为不同的值,则您的应用程序和C库将不一致,并且情况会变差。如果您有引用声称可以重新定义是安全的,那么FD_SETSIZE我很希望看到它们。
Jean-Paul Calderone 2014年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.