Keuin's

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

优点:

缺点:

Kernel AIO(KAIO)

从Linux 2.6开始提供。这是一个基于状态机的内核态异步IO实现,最初的设计意图是提供异步磁盘IO操作(Asynchronous disk IO)。

因为它的接口还没有稳定下来,所以libc不提供包装函数,需要直接使用系统调用,或者使用内核组织提供的外部库libaio

优点:

缺点:

io_uring精确地解决了上述Kernel AIO的问题。

epoll (EPOLLET) + non-blocking socket (O_NONBLOCK)

优点:

缺点:

io_uring 面向性能的设计

Interface of 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的支持情况

一些对io_uring的批评

io_uring的设计非常优秀,理论上讲应该在多数情况获得比上述三种方案更好的性能,但事实并非总是这样。

这个issue讨论了在一些基准测试中,io_uring要比epoll慢的现象。讨论也涉及了部分开发者吹捧io_uring、忽视批评的行为。

这篇博客在Rust+gRPC场景下做了epoll vs io_uring的基准测试。结果显示,epoll方案的性能及可伸缩性要高于io_uring方案。

后记

Windows也开始抄io_uring了:IoRing

相关阅读