在学习epoll的时候,一般都会学习到epoll的水平触发和边缘触发模式。
很多地方都有提到一个结论:边缘触发(ET)模式+将监听的socket的fd设置为non_blocking,可以提高效率,但对于这个增加了多少效率,并没有相关的benchmark可以看到,故整理转载了两种模式对于性能的影响分析。
以下:
转自:dong
epoll的帮助中指出,使用ET模式,可以便捷的处理EPOLLOUT事件,省去打开与关闭EPOLLOUT的epoll_ctl(EPOLL_CTL_MOD)调用。从而有可能让你的性能得到一定的提升。
例如你需要写出1M的数据,写出到socket 256k时,返回了EAGAIN,ET模式下,当再次返回EPOLLOUT时,继续写出待写出的数据,当没有数据需要写出时,不处理直接略过即可。而LT模式则需要先打开EPOLLOUT,当没有数据需要写出时,再关闭EPOLLOUT(否则会一直会返回EPOLLOUT事件)
总体来说,ET处理EPOLLOUT方便高效些,LT不容易遗漏事件、不易产生bug
ET理论上可以比LT少带来一些系统调用,所以更省一些。具体的性能提高有多少,要看应用场景。不过绝大部分场景下,LT是足够的。
但是使用水平触发,可能会导致:
使用了epoll,且你的fd是阻塞的。
如果epoll通知你fd可以写数据了。然后你啦啦啦的打算写100B数据,但是内核buf只有50B的可用空间,这时候,你的进程就被阻塞了。
所以说,用了epoll不能保证你的进程在读写的时候不会阻塞,在这种极端情况下,用边缘触发会带来更好的性能。
但是对于现代CPU来说, IO总是能先满的, 相对于龟速的网络, 多几个system call无所谓吧
比如,以单线程速度见长的Redis使用的是水平触发。
在边缘触发(ET)模式下:
EPOLLOUT事件:
EPOLLOUT事件在连接时建立时首先触发触发一次,表示可写,其他时候的触发条件为:
1.某次write,写满了发送缓冲区,返回错误码为EAGAIN。
2.对端读取了一些数据,又重新可写了,此时会触发EPOLLOUT。
简单地说:EPOLLOUT事件只有socket从unwritable变为writable时,才会触发一次。对于EPOLLOUT事件,必须要将该文件描述符缓冲区一直写满,让 errno 返回 EAGAIN 为止,或者发送完所有数据为止。
EPOLLIN事件:
EPOLLIN事件则只有当对端有数据写入时才会触发,所以触发一次后需要不断读取所有数据直到读完EAGAIN为止,否则剩下的数据只有在下次对端有写入时才能一起取出来了。设想这样一个场景:接收端接收完整的数据后会向对端发送应答报文,对端才会继续向接收端发送数据,从而触发下一次的EPOLLIN,而这时没有读完socket缓冲区中的所有数据,导致接收端无法向对端发送应答报文,而对端没有收到应答报文,也就不会再发送数据触发下一次的EPOLLIN,而没有下一次的EPOLLIN事件,接收端也就永远不知道此socket缓冲区中还有未读出的数据。(一个完美的死循环)
简单的说:EPOLLIN事件只有对端新数据写入时,才会触发一次。对于EPOLLIN事件,必须要将该文件描述符一直读到空,让 errno 返回 EAGAIN 为止。
1.对于读:由于需要一直读直到把数据读完,所以大家在编写程序的时候一般会用一个循环一直读取socket,那这个循环势必会在最后一次阻塞,即没有数据可读的情况下,阻塞式socket会在数据读完之后一直阻塞下去,而非阻塞式的socket则返回<0,并让errno 返回 EAGAIN 。
2.对于写,当使用阻塞式socket时,socket的unwritable/writable状态变化没有任何意义!!因为此时无论发送多大的数据write总是会阻塞直到所有数据都发送出去。(也就是说,边缘触发的epoll如果不和非阻塞的socket搭配,使用起来会产生问题)
所以需要套接字设置为非阻塞的。
reference
https://www.zhihu.com/question/23614342