您的位置:首页 > 其它

I/O多路复用之比较select&poll&epoll

2017-07-20 16:35 323 查看
终于把I/O多路复用的几篇总结写完了,也算是下了不少功夫吧。期间收到私信,说希望我能持久更新。很开心自己写的东西,能够帮到别人。好了,废话不多说了,通过这篇博客总结一下三种多路复用I/O的异同。

前面我们讨论了select,poll和epoll三组I/O复用系统调用,这3组系统调用都能同时监听多个文件描述符。它们将等待由timeout参数指定的超时时间,直到一个或者多个文件描述符上有事件发生时返回,返回值是就绪的文件描述符的数量。

select, poll, epoll 都是I/O多路复用的具体的实现,他们出现是有先后顺序的。根据我们的日常经验,一般后出现的都会比早出现的更好些。事实上也确实是这样。

I/O多路复用这个概念被提出来以后, select是第一个实现 (1983 左右在BSD里面实现的)。

select 被实现以后,很快就暴露出了很多问题。比如:

select 会修改传入的参数数组,这个对于一个需要调用很多次的函数,是非常不友好的。

select 如果任何一个sock(I/O stream)出现了数据,select 仅仅会返回,但是并不会告诉你是那个sock上有数据,于是你只能自己一个一个的找。

select 只能监视1024个链接(32位机器下),linux 定义在头文件中的,参见FD_SETSIZE。

于是14年以后(1997年)一帮人又实现了poll, poll 修复了select的很多问题,比如:

poll 去掉了1024个链接的限制。

不用再修改传入数组。

其实拖14年那么久也不是效率问题, 而是那个时代的硬件实在太弱,一台服务器处理1千多个链接简直就是神一样的存在了,select很长段时间已经满足需求。

虽然poll在select基础上改进了很多,方便了用户的使用。但是还是存在一些问题没有改进,poll的实现机制跟select是一样的,其实两者的差别不是很大。

后来就有了epoll。

从实现原理上来说,select和poll采用的都是轮询的方式,即每次调用都要扫描整个注册文件描述符集合,并将其中就绪的文件描述符返回给用户程序,因此它们检测就绪事件的算法的时间复杂度是O(n)。epoll_wait()则不同,它采用的是回调的方式。内核检测到就绪的文件描述符时,将触发回调函数,回调函数就将该文件描述符上对应的事件插入内核就绪事件队列。内核最后在适当的时机将该就绪事件队列中的内容拷贝到用户空间。因此epoll_wait无须轮询整个文件描述符集合,所以其时间复杂度为O(1)。

同时,由于每次select和poll调用都返回整个用户注册的事件集合,所以应用程序索引就绪文件描述符的时间复杂度为O(n)。epoll_wait系统调用的events参数仅用来返回就绪的事件,这使得应用程序索引就绪文件描述符的时间复杂度为O(1)。

为了简洁,清晰的比较三者的异同,接下来都以表格的形式列出。

系统调用selectpollepoll
事件集合用户通过3个参数分别传入感兴趣的可读,可写及异常等事件,内核通过对这些参数的在线修改来反馈其中的就绪事件。这使得用户每次调用select都要重置这3个参数统一处理所有事件类型,因此只需要一个事件集参数。用户通过pollfd.events传入感兴趣的事件,内核通过修改pollfd.revents反馈其中就绪的事件内核通过一个事件表直接管理用户感兴趣的所有事件。因此每次调用epoll_wait时,无须反复传入用户感兴趣的事件。epoll_wait系统调用参数events仅用来反馈就绪的事件
应用程序索引就绪文件描述符的时间复杂度O(n)O(n)O(1)
最大支持文件描述符数由FD_SETSIZE决定/proc/sys/fs/file-max规定,最大打开的文件描述符数/proc/sys/fs/file-max规定,最大打开的文件描述符数
工作模式LTLT默认是LT,支持ET
内核实现和工作效率采用轮询方式来检测就绪事件,算法时间复杂度为O(n)采用轮询方式来检测就绪事件,算法时间复杂度为O(n)采用回调方式来检测就绪事件,算法时间复杂度为O(1)
I/O多路复用原理
selectselect本质上是通过设置或者检查存放fd标志位的数据结构来进行下一步处理。这样所带来的缺点是:
1.单个进程可监视的fd数量被限制
2.需要维护一个用来存放大量fd的数据结构,这样会使得用户空间和内核空间在传递该结构时复制开销大
3.对socket进行扫描时是线性扫描
pollpoll本质上和select没有区别,它将用户传入的数组拷贝到内核空间,然后查询每个fd对应的设备状态,如果设备就绪则在设备等待队列中加入一项并继续遍历,如果遍历完所有fd后没有发现就绪设备,则挂起当前进程,直到设备就绪或主动超时,唤醒后它又要在此遍历fd。这个过程经历了多次无谓的遍历。和select一样,存在大量的fd的数组被整体复制于用户态和内核地址空间之间的缺点。
epoll在前面说的复制问题上,epoll使用mmap减少复制开销。还有一个特点是epoll采用事件就绪通知方式。
I/O多路复用FD剧增后带来的IO效率问题
select因为每次调用时都会对连接进行线性遍历,所以随着FD的增加会造成遍历速度慢的“线性下降性能问题”
poll同上
epoll因为epoll内核中实现是根据每个fd上的callback函数来实现的,只有活跃的socket才会主动调用callback,所以在活跃socket较少的情况下,使用epoll没有前面两者的线性下降问题,但是所有socket都很活跃的情况下,性能会下降。
关于三者随监听文件描述符增加的IO效率,可以参考下面的一些数据,就很容易看出:

ps:基于测试目的,已将select的FD_SETSIZE修改为16384,以此允许测试程序在使用select()时能监听大量文件描述符。

被监听的文件描述符数量select()所占用cpu时间(秒)poll()所占用cpu时间(秒)epoll()所占用cpu时间(秒)
100.730.610.41
1003.02.90.42
100035350.53
100009309900.66
很容易看出,当随着监听文件描述符的剧增,select,poll的效率跟epoll相差甚远。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: