提问人:levzettelin 提问时间:7/2/2023 更新时间:7/4/2023 访问量:980
epoll 是比 io_uring 更好的 API 吗?
Is epoll a better API than io_uring?
问:
使用 io_uring 时,您必须在上一个读取请求完成时提交新的读取请求。这在很多情况下是不自然的,因为您通常只想继续从 TCP 连接读取数据。使用 epoll,您只需在内核的 epoll 对象中注册一次文件句柄,然后每当有新数据可供读取时,您就会收到通知。(当然,什么是“自然”是主观的。
当然,epoll 存在一个问题,您必须重复进行“读取”系统调用才能获得实际数据,在这方面io_uring显然更好。所以我的陈述主要与API的抽象语义有关。但是,我也可以看到重复读取请求可能会在io_uring中造成性能问题的情况,例如,对于具有大量连接(例如,20k)的服务器,这些服务器都执行大量非常短的读取(例如,4 字节)。
我在这里遗漏了什么吗?io_uring是否可以在单个提交队列条目 (sqe) 可能导致多个完成队列条目 (cqe) 的模式下使用?
答:
基本说明
就 API 而言,proactor 模式 (, , ) 优于 reactor (, , 等),因为它实际上模仿了自然的程序控制流:你“调用”一些异步函数(通过调度它执行),然后通过读取完成队列或等待“完成端口”来等待结果。io_uring
IOCP
ioring
epoll
kqueue
在阻塞模式下,典型代码如下所示(伪代码):
char[255] buf;
int ret = recv(socket, &buf, sizeof(buf), 0);
// Now we have the buffer and the number of bytes read
// ...
proactor 模式中的非阻塞模式类似,只是我们可以一次发出多个系统调用(再次是伪代码):
char[255] buf1;
char[255] buf2;
char[255] buf3;
int ret1, ret2, ret3 = wait(
recv(socket1, &buf1, sizeof(buf1), 0),
recv(socket2, &buf2, sizeof(buf2), 0),
recv(socket3, &buf3, sizeof(buf3), 0)
);
// Now we have all the buffers and return values
// ...
该模型不仅减轻了程序员的心理负担,而且还通过利用内核线程在后台的多个 CPU 内核之间共享工作负载的可能性。这种缩放对文件 IO 特别有利,因为没有默认方法可以在不阻塞线程的情况下进行真正的异步读取或写入调用。
之前 Linux 尝试像 POSIX AIO 一样做异步文件 IO 非常有限且相当丑陋,因此这是朝着正确方向迈出的进化一步。io_uring
但是,proactor 模式显然有一些缺点,例如需要将缓冲区保留在 RAM 中,以便进行每次正在进行的读取/接收调用。起初可以忽略不计,但是一旦您必须处理许多连接,您将需要大量未被积极利用的内存,只是等待完成。
io_uring
试图通过提供缓冲池工具来部分解决这个问题,但这仍然远不及使用单线程事件循环所能做的事情。epoll
重复调度问题
至于您的重复调度问题,实际上为它的一些调用提供了“multishot”模式:io_uring
AFAIK,超时也支持这种模式,这实际上将它们变成了计时器。但主要问题是它仍在开发中,因此其中一些功能仅在最新的 Linux 内核 (6.0+) 中可用。io_uring
总结
所以答案是:是更好的 API,它有价格,但可以处理多线程、文件 IO 和其他开箱即用的东西。另一方面,它提供了对缓冲和函数调用的更精细的控制,但是一旦您需要处理文件(或多个线程),您就只能靠自己了。io_uring
epoll
epoll
可能仍然与低内存设备相关,但在现代系统上,规划支持会更有益,因为它可能会取代 、 和将来。io_uring
select
poll
epoll
然而,由于它仍在开发中,它一直是危险漏洞的来源,因此像谷歌这样的一些公司正在搁置它。在两者之间进行选择时,这一事实也值得考虑。io_uring
评论