io_uring:Linux新的异步IO API
在异步IO(asynchronous input/output)这件事上,Windows走在了Linux的前面(IOCP, RIO)。这个问题很好地说明了这一点:
I’m comparing Linux file I/O to Windows, and I can’t see how epoll will help a Linux program at all. The kernel will tell me that the file descriptor is “ready for reading,” but I still have to call blocking read() to get my data, and if I want to read megabytes, it’s pretty clear that that will block. On Windows, I can create a file handle with OVERLAPPED set, and then use non-blocking I/O, and get notified when the I/O completes, and use the data from that completion function. I need to spend no application-level wall-clock time waiting for data, … If I have to emulate asynchronous I/O on Linux, then I have to allocate some number of threads to do this, and those threads will spend a little bit of time doing CPU things, and a lot of time blocking for I/O, plus there will be overhead in the messaging to/from those threads. Thus, I will either over-subscribe or under-utilize my CPU cores.
在io_uring出现之前,在Linux里大家都用epoll或者select,通过IO多路复用来避免阻塞IO。但是,epoll和select并没有彻底解决问题:当文件可读时,读操作仍需要应用程序自己完成,这个操作很显然是阻塞的。如果数据量很大(如1MB),很明显这要阻塞一段时间。类比copy loop和DMA的关系,能不能让kernel帮我们把数据加载好,以便应用程序可以在这段时间做其他事情(而不是阻塞)呢?这是io_uring要解决的问题之一。
io_uring
(旧称aioring
)是来自Facebook的Jens Axboe提出的一组新的异步IO系统调用,为Linux提供类似Windows NT IOCP(I/O Completion Port)的异步IO机制。io_uring于Linux 5.1被合并到内核主线。
Jens Axboe (born circa 1976) is a Linux kernel hacker. Axboe is the current Linux kernel maintainer of the block layer and other block devices, along with contributing the CFQ I/O scheduler, Noop scheduler, Deadline scheduler and splice IO architecture.
问题背景
io_uring是Linux的一组新的异步IO系统调用,提出它的目的是解决已有的Kernel AIO的一些设计缺陷。这些缺陷不仅影响性能(它有时阻塞!),还使得它很难用。
(在这封邮件中,可以看到Linus痛骂已有的Kernel AIO设计之差;但是由于Linus著名的“不破坏用户态”的承诺,内核仍需继续维护这个API)
因此,io_uring面向希望提升现有IO密集型应用程序性能(高吞吐量、低延迟)的用户。有了io_uring之后,Linux在异步IO上终于可以和Windows NT对标了。
Linux已有的异步IO方法
POSIX AIO
aio_*(3)
https://man7.org/linux/man-pages/man7/aio.7.html
优点:
- 兼容性最好(OS、文件系统、是否有缓冲)
缺点:
- 性能差。为了跨平台,使用了用户态实现(glibc),底层是多线程模型,队列深度受限于线程数量
Kernel AIO(KAIO)
从Linux 2.6开始提供。这是一个基于状态机的内核态异步IO实现,最初的设计意图是提供异步磁盘IO操作(Asynchronous disk IO)。
因为它的接口还没有稳定下来,所以libc不提供包装函数,需要直接使用系统调用,或者使用内核组织提供的外部库libaio
。
优点:
- 内核态的基于状态机的实现,比POSIX AIO的性能、可伸缩性(scalability)更好
缺点:
- Linux专有,其他UNIX系统(不一定)支持(和POSIX AIO相比)
- 不够成熟(暂不能用此重新实现POSIX AIO,故POSIX AIO还是用户态实现)
- 有些情况下仍会阻塞:
- 仍有明显的欠优化设计
- 一个IO操作需要至少2个系统调用(执行、读取结果),且传递参数、向调用方返回值时存在冗余的内存拷贝(Linux 4.18引入了新的轮询机制
IOCB_CMD_POLL
。类似io_uring,通过将一个环形缓冲区从内核映射到用户态,可以将这里的系统调用次数减少到1)
- 一个IO操作需要至少2个系统调用(执行、读取结果),且传递参数、向调用方返回值时存在冗余的内存拷贝(Linux 4.18引入了新的轮询机制
io_uring精确地解决了上述Kernel AIO的问题。
epoll
(EPOLLET
) + non-blocking socket (O_NONBLOCK
)
优点:
- 自Linux 2.5.44被引入内核,兼容性好
- 比其他两种方案(POSIX AIO、Kernel AIO)的性能更好
缺点:
- 只支持网络套接字
io_uring 面向性能的设计
完全异步的接口
接口不会阻塞。
零拷贝(zero copy)通信
io_uring的输入输出均在共享缓冲区中进行,避免传参和返回值引入的冗余内存复制操作
最小化的(minimal)系统调用用量
修复熔断和幽灵安全漏洞的补丁为系统调用引入了十分显著的性能开销,因此现在的设计需要尽量减少系统调用次数。
io_uring可以在一个系统调用中发起多个IO操作,可以有效减少系统调用次数。在轮询(poll)模式下,内核甚至可以主动读取新增的任务,而不需要用户执行系统调用来提交。
一个io_uring实例:echoserver
frevib给出了两个echoserver实现(分别使用epoll和io_uring),并用他们做了基准测试。
从测试结果中可以看出,io_uring实现的性能要明显好于epoll的实现,这个差距在连接数增大时愈发明显。
https://github.com/frevib/io_uring-echo-server/blob/io-uring-feat-fast-poll/io_uring_echo_server.c
https://github.com/frevib/epoll-echo-server/blob/master/epoll_echo_server.c
通过对照源代码,可以发现io_uring的echoserver写法与epoll的十分类似。
这里还有一个更简单的例子:cat
。我们发现,使用liburing
比直接使用io_uring系统调用方便得多(实际上,使用syscall wrapper能以非常小的性能开销,换取开发效率的明显提升和样板代码(boilerplate code)的减少)。
已有项目对于io_uring的支持情况
- Nginx: 以前使用epoll,现在增加了io_uring支持
- RocksDB: WIP ([1] [2])
- Nodejs, uvloop (均由libuv驱动): WIP
- tokio: 于tokio-uring增加了对io_uring的支持
- Python世界: 主要问题是asyncio不支持异步文件io,现有的aiofile实现是基于线程池的,有类似POSIX AIO的缺点。
- libuv缺少人手维护,PR还没合并
- kLoop基于io_uring
- cpython维护者认为io_uring迭代太快,接口不稳定,暂时不整合到cpython中,应该在PyPI里的第三方库实现(如uring_file)
- Go世界: netpoll WIP, internal/poll WIP
一些对io_uring的批评
io_uring的设计非常优秀,理论上讲应该在多数情况获得比上述三种方案更好的性能,但事实并非总是这样。
这个issue讨论了在一些基准测试中,io_uring要比epoll慢的现象。讨论也涉及了部分开发者吹捧io_uring、忽视批评的行为。
这篇博客在Rust+gRPC场景下做了epoll vs io_uring的基准测试。结果显示,epoll方案的性能及可伸缩性要高于io_uring方案。
后记
Windows也开始抄io_uring了:IoRing
- https://windows-internals.com/i-o-rings-when-one-i-o-operation-is-not-enough/
- https://windows-internals.com/ioring-vs-io_uring-a-comparison-of-windows-and-linux-implementations/
相关阅读
- https://kernel.dk/io_uring.pdf
- https://kernel.dk/axboe-kr2022.pdf
- https://unixism.net/loti/
- https://unixism.net/loti/what_is_io_uring.html
- https://stackoverflow.com/a/8782305/14332799
- https://lwn.net/Articles/743714/
- https://blog.cloudflare.com/io_submit-the-epoll-alternative-youve-never-heard-about/
- https://lkml.iu.edu/hypermail/linux/kernel/0305.2/0697.html
- https://tokio.rs/blog/2021-07-tokio-uring
- https://www.51cto.com/article/669687.html
- https://hackmd.io/@shanvia/B1Ds1vlAD
- https://developer.aliyun.com/article/834974
- https://www.snia.org/sites/default/files/SDC/2019/presentations/Storage_Performance/Kariuki_John_Verma_Vishal_Improved_Storage_Performance_Using_the_New_Linux_Kernel_I.O_Interface.pdf
- https://www.youtube.com/watch?v=vjudg0lCEhQ
- https://stackoverflow.com/a/57451551
- https://icebergu.com/archives/go-iouring
- https://www.scylladb.com/2020/05/05/how-io_uring-and-ebpf-will-revolutionize-programming-in-linux/
- https://windows-internals.com/ioring-vs-io_uring-a-comparison-of-windows-and-linux-implementations/
- https://github.com/iamwwcposts/articles/issues/15