Linux 上真的没有异步块 I/O 吗?
- 2024-10-17 08:47:00
- admin 原创
- 62
问题描述:
考虑一个受 CPU 限制但也有高性能 I/O 要求的应用程序。
我正在将 Linux 文件 I/O 与 Windows 进行比较,我看不出 epoll 对 Linux 程序有什么帮助。内核会告诉我文件描述符“已准备好读取”,但我仍然必须调用阻塞 read() 来获取数据,如果我想读取兆字节,很明显这将被阻塞。
在 Windows 上,我可以创建一个设置了 OVERLAPPED 的文件句柄,然后使用非阻塞 I/O,并在 I/O 完成时收到通知,然后使用该完成函数中的数据。我不需要花费应用程序级别的挂钟时间来等待数据,这意味着我可以根据内核数量精确调整线程数,并获得 100% 的高效 CPU 利用率。
如果我必须模拟 Linux 上的异步 I/O,那么我必须分配一些线程来执行此操作,这些线程将花费一点时间执行 CPU 操作,并花费大量时间阻塞 I/O,此外,这些线程之间的消息传递也会产生开销。因此,我要么过度订阅 CPU 核心,要么未充分利用 CPU 核心。
我将 mmap() + madvise() (WILLNEED) 视为“穷人的异步 I/O”,但它仍然没有完全完成,因为当它完成时我无法收到通知——我必须“猜测”,如果我猜“错了”,我最终会阻塞内存访问,等待数据来自磁盘。
Linux 似乎在 io_submit 中已经开始使用异步 I/O,而且它似乎也有一个用户空间 POSIX aio 实现,但是这种情况已经持续了一段时间了,而且据我所知没有人会保证这些系统适用于关键的高性能应用程序。
Windows 模型的工作原理大致如下:
发出异步操作。
将异步操作绑定到特定的 I/O 完成端口。
等待该端口上的操作完成
当 I/O 完成时,等待端口的线程解除阻塞,并返回对待处理 I/O 操作的引用。
步骤 1/2 通常作为单个操作完成。步骤 3/4 通常由工作线程池完成,不一定是发出 I/O 的同一个线程。此模型与 boost::asio 提供的模型有些相似,只是 boost::asio 实际上不提供基于块的异步(磁盘)I/O。
与 Linux 中的 epoll 的不同之处在于,在步骤 4 中,尚未发生任何 I/O ——它会提升步骤 1 到步骤 4 之后,如果您已经确切知道自己需要什么,那么这就是“向后”的。
我编写了大量嵌入式、桌面和服务器操作系统,可以说这种异步 I/O 模型对于某些类型的程序来说非常自然。它还具有很高的吞吐量和很低的开销。我认为这是 Linux I/O 模型在 API 级别上仍然存在的真正缺点之一。
解决方案 1:
(2020) 如果您使用的是5.1 或更高版本的 Linux 内核,您可以使用类似文件的 I/Oio_uring
接口并获得出色的异步操作。
与现有的libaio
/KAIO接口相比,io_uring
具有以下优点:
在执行缓冲 I/O 时保留异步行为(而不仅仅是在执行直接 I/O 时)
更易于使用(尤其是使用
liburing
辅助库时)可以选择以轮询方式工作(但您需要更高的权限才能启用此模式)
减少每次 I/O 的簿记空间开销
由于用户空间/内核系统调用模式切换更少,从而降低了 CPU 开销(由于spectre/meltdown 缓解措施的影响,这在当今尤为重要)
可以预先注册文件描述符和缓冲区以节省映射/取消映射时间
更快(可以实现更高的总吞吐量,I/O 延迟更低)
“链接模式”可以表达 I/O 之间的依赖关系(>=5.3 内核)
可以使用基于套接字的 I/O(从 >=5.3 开始支持
recvmsg()
/ ,请参阅io_uring.c 的 git 历史记录sendmsg()
中提到支持一词的消息)支持尝试取消排队 I/O (>=5.5)
可以请求始终从异步上下文执行 I/O,而不是默认仅在内联提交路径触发阻塞时才将 I/O 转移到异步上下文(>=5.6 内核)
越来越多地支持执行超出
read
/write
范围的异步操作(例如fsync
(>=5.1)、fallocate
(>=5.6)、splice
(>=5.7) 等)更高的发展动力
不会因为星星没有完全对齐而造成阻碍
相对于glibc的POSIX AIO来说,io_uring
有以下优点:
速度更快,效率更高(上述较低开销的好处在这里更加适用)
接口由内核支持,不使用用户空间线程池
执行缓冲 I/O 时,数据副本较少
无需与信号搏斗
Glibc 的 POSIX AIO 不能在单个文件描述符上进行多于一个的 I/O,而
io_uring
大多数肯定可以!
使用 io_uring实现高效IO 的文档更详细地介绍了io_uring
的好处和用法。io_uring的新增功能文档描述了在io_uring
5.2 - 5.5 内核之间添加的新功能,而LWN 文章 io_uring 的快速发展描述了 5.1 - 5.5 内核中可用的功能,并展望了 5.6 中将包含哪些功能(另请参阅LWN 的 io_uring 文章列表)。此外,作者 Jens Axboe 于2019 年末推出了通过 io_uring 内核配方实现更快的 IO 视频演示(幻灯片),并于 2022 年中推出了 io_uring 内核配方的新增功能视频演示(幻灯片)io_uring
。最后,io_uring 教程之王介绍了io_uring
用法。
可以通过io_uring 邮件列表io_uring
联系社区,io_uring邮件列表存档显示了 2021 年初的每日流量。
recv()
关于“支持vs意义上的部分 I/O read()
”:5.3 内核中有一个补丁,它将自动重试io_uring
短读取,5.4 内核中还有一个提交,它将行为调整为仅在处理未设置标志的请求的“常规”文件时自动处理短读取REQ_F_NOWAIT
(看起来您可以REQ_F_NOWAIT
通过IOCB_NOWAIT
或通过使用 打开文件来请求)。因此,您也可以从中O_NONBLOCK
获得“短”I/O 行为。recv()
`io_uring`
使用软件/项目io_uring
尽管该界面还很年轻(其第一个版本于 2019 年 5 月问世),但一些开源软件正在io_uring
“广泛”使用它:
fio(也是由 Jens Axboe 编写)有一个io_uring ioengine后端(实际上它是在 2019 年 2 月的 fio-3.13 中引入的!)。两位英特尔工程师的“使用新的 Linux 内核 I/O 接口 SNIA 演示文稿提高存储性能”(幻灯片
io_uring
)指出,当将ioengine 与Optane 设备上的ioengine 进行比较时,他们能够在一个工作负载上获得两倍的 IOPS,而在另一个工作负载上,队列深度为 1 时的平均延迟不到一半libaio
。SPDK项目在其 v19.04 版本中添加了使用 io_uring (!) 进行块设备访问的支持(但显然这不是您通常使用 SPDK 进行基准测试以外的后端)。最近,他们似乎还在 v20.04 中添加了将其与套接字一起使用的支持……
Ceph 于 2019 年 12 月提交了 io_uring 后端,这是其 15.1.0 版本的一部分。提交作者发布了一条 github 评论,显示某些io_uring 后端与 libaio 后端相比,根据工作负载的不同,有优有劣(在 IOPS、带宽和延迟方面)。
RocksDB于 2019 年 12 月
io_uring
为 MultiRead 提供了后端,并成为其6.7.3 版本的一部分。Jens 表示io_uring
这有助于大幅缩短延迟。libev 于 2019 年 12 月发布了带有初始
io_uring
后端的4.31 版本。虽然作者最初的一些观点在较新的内核中得到了解决,但在撰写本文时(2021 年中), libev 的作者对其成熟度做出了一些评论io_uring
,并在实施进一步改进之前采取了观望态度。QEMU 于 2020 年 1 月提交了一个 io_uring 后端,它是QEMU 5.0 版本的一部分。在“ QEMU 中的 io_uring:Linux 的高性能磁盘 IO ” PDF 演示文稿中,Julia Suvorova 展示了该
io_uring
后端在一个随机 16K 块的工作负载上的表现优于threads
和后端。aio
Samba 于 2020 年 2 月合并了一个
io_uring
VFS 后端,并成为Samba 4.12 版本的一部分。在“Linux io_uring VFS 后端” Samba 邮件列表线程中,Stefan Metzmacher(提交作者)表示,该io_uring
模块在综合测试中能够将吞吐量提高大约 19%(与某些未指定的后端相比)。您还可以阅读Stefan 的“Async VFS Future” PDF 演示文稿,了解这些变化背后的一些动机。Facebook 的实验性 C++ libunifex使用它(但您还需要 5.6+ 内核)
Rust 社区一直在编写包装器,以便更
io_uring
轻松地访问纯 Rust。rio是一个被讨论过的库,作者说,与使用包装在线程中的同步调用相比,它们实现了更高的吞吐量。作者在 FOSDEM 2020 上介绍了他的数据库和库,其中包括一个赞扬优点的部分。io_uring
Glommio专门使用rust库
io_uring
。作者 (Glauber Costa) 发表了一篇名为《现代存储速度很快》的文档。API 很糟糕,表明经过仔细调整后,Glommio 在 Optane 设备上执行顺序 I/O 时可以获得比常规(非io_uring
)系统调用高 2.5 倍的性能。Gluster于 2020 年 10 月合并了 io_uring posix xlator ,并成为Gluster 9.0 版本的一部分。提交作者提到性能“并不比常规的 pwrite/pread 系统调用差”。
软件调查使用io_uring
PostgreSQL 开发人员 Andres Freund 是改进的推动力之一
io_uring
(例如,减少文件系统 inode 争用的解决方法)。有一个演示文稿“PostgreSQL 的异步 IO”(请注意,视频在 5 分钟前就坏了)(PDF)激发了对 PostgreSQL 进行更改的需要,并展示了一些实验结果。他表示希望将他的可选io_uring
支持纳入 PostgreSQL 14,并且似乎敏锐地意识到甚至在内核级别什么有效,什么无效。2020 年 12 月,Andres 在“阻塞 I/O、异步 I/O 和 io_uring”pgsql-hackers 邮件列表线程中进一步讨论了他的 PostgreSQLio_uring
工作,并提到正在进行的工作可以在https://github.com/anarazel/postgres/tree/aio中看到。Netty项目有一个孵化器仓库,正在
io_uring
支持需要 5.9 内核libuv 有一个针对其添加
io_uring
支持的拉取请求,但其在项目中的进展缓慢SwiftNIO 在 2020 年 4 月增加了
io_uring
对事件的支持(但不包括系统调用),Linux:完整的 io_uring I/O问题概述了进一步整合它的计划Tokio Rust 项目已经开发出一个概念验证tokio-uring
Linux 发行版支持io_uring
(2020 年末)Ubuntu 18.04 的最新 HWE 支持内核是 5.4,因此
io_uring
可以使用系统调用。此发行版未预先打包liburing
帮助程序库,但您可以自行构建。Ubuntu 20.04 的初始内核是 5.4,因此
io_uring
可以使用系统调用。如上所述,该发行版没有预先打包liburing
。Fedora 32 的初始内核是 5.6 ,并且已经打包
liburing
,io_uring
可以使用。SLES 15 SP2 有一个 5.3 内核,因此
io_uring
可以使用系统调用。此发行版未预先打包liburing
帮助程序库,但您可以自行构建。(2021 年中)RHEL 8 的默认内核不支持
io_uring
(此答案的先前版本错误地表示它支持)。有一篇添加 io_uring 支持 Red Hat 知识库文章(内容位于订阅者付费墙后面)正在“进行中”。(2022 年中)RHEL 9 的默认内核不支持
io_uring
。内核足够新(5.14),但io_uring
明确禁用了对的支持。
希望io_uring
能够为 Linux 带来更好的异步文件 I/O 故事。
(为了给这个答案增加一点可信度,在过去的某个时候,Jens Axboe(Linux 内核块层维护者和发明者io_uring
)认为这个答案可能值得投票:-)
解决方案 2:
真正的答案,由 Peter Teoh 间接指出,基于 io_setup() 和 io_submit()。具体来说,Peter 指出的“aio_”函数是基于线程的 glibc 用户级仿真的一部分,这不是一种有效的实现。真正的答案是:
io_submit(2)
io_setup(2)
io_cancel(2)
io_destroy(2)
io_getevents(2)
请注意,2012-08 的手册页指出,此实现尚未成熟到可以取代 glibc 用户空间模拟的程度:
http://man7.org/linux/man-pages/man7/aio.7.html
该实现尚未成熟到可以使用内核系统调用完全重新实现 POSIX AIO 实现的程度。
因此,根据我能找到的最新内核文档,Linux 尚未拥有成熟的基于内核的异步 I/O 模型。而且,如果我假设文档中的模型实际上已经成熟,它仍然不支持 recv() 与 read() 意义上的部分 I/O。
解决方案 3:
详见:
http://code.google.com/p/kernel/wiki/AIOUserGuide
还有这里:
http://www.ibm.com/developerworks/library/l-async/
Linux 确实在内核级别提供了异步块 I/O,API 如下:
aio_read Request an asynchronous read operation
aio_error Check the status of an asynchronous request
aio_return Get the return status of a completed asynchronous request
aio_write Request an asynchronous operation
aio_suspend Suspend the calling process until one or more asynchronous requests have completed (or failed)
aio_cancel Cancel an asynchronous I/O request
lio_listio Initiate a list of I/O operations
如果你问这些 API 的用户是谁,答案是内核本身——这里只显示了一小部分:
./drivers/net/tun.c (for network tunnelling):
static ssize_t tun_chr_aio_read(struct kiocb *iocb, const struct iovec *iv,
./drivers/usb/gadget/inode.c:
ep_aio_read(struct kiocb *iocb, const struct iovec *iov,
./net/socket.c (general socket programming):
static ssize_t sock_aio_read(struct kiocb *iocb, const struct iovec *iov,
./mm/filemap.c (mmap of files):
generic_file_aio_read(struct kiocb *iocb, const struct iovec *iov,
./mm/shmem.c:
static ssize_t shmem_file_aio_read(struct kiocb *iocb,
ETC。
在用户空间级别,还有 io_submit() 等 API(来自 glibc),但以下文章提供了使用 glibc 的替代方法:
http://www.fsl.cs.sunysb.edu/~vass/linux-aio.txt
它直接将 io_setup() 等函数的 API 实现为直接系统调用(绕过 glibc 依赖项),应该存在通过相同“__NR_io_setup”签名的内核映射。在以下位置搜索内核源代码:
http://lxr.free-electrons.com/source/include/linux/syscalls.h#L474 (此 URL 适用于最新版本 3.13)你会看到内核中这些 io_*() API 的直接实现:
474 asmlinkage long sys_io_setup(unsigned nr_reqs, aio_context_t __user *ctx);
475 asmlinkage long sys_io_destroy(aio_context_t ctx);
476 asmlinkage long sys_io_getevents(aio_context_t ctx_id,
481 asmlinkage long sys_io_submit(aio_context_t, long,
483 asmlinkage long sys_io_cancel(aio_context_t ctx_id, struct iocb __user *iocb,
glibc 的后续版本应该使得使用“syscall()”来调用 sys_io_setup() 变得没有必要,但如果没有最新版本的 glibc,如果您使用的是具有“sys_io_setup()”功能的后续内核,您总是可以自己进行这些调用。
当然,还有其他用于异步 I/O 的用户空间选项(例如,使用信号?):
http://personal.denison.edu/~bressoud/cs375-s13/supplements/linux_altIO.pdf
或者可能:
POSIX 异步 I/O(AIO)的状态如何?
“io_submit” 和相关函数在 glibc 中仍然不可用(参见 io_submit 手册页),我已在我的 Ubuntu 14.04 中进行了验证,但此 API 是 Linux 特有的。
其他如 libuv、libev 和 libevent 也是异步 API:
http://nikhilm.github.io/uvbook/filesystem.html#reading-writing-files
http://software.schmorp.de/pkg/libev.html
所有这些 API 都旨在实现在 BSD、Linux、MacOSX 甚至 Windows 之间的移植。
在性能方面,我没有看到任何数字,但怀疑 libuv 可能是最快的,因为它的轻量级?
https://ghc.haskell.org/trac/ghc/ticket/8400
解决方案 4:
对于网络套接字 i/o,当它处于“就绪”状态时,它不会阻塞。这就是O_NONBLOCK
“就绪”的含义。
对于磁盘 i/o,我们有posix aio、linux aio、sendfile和朋友。
- 2024年20款好用的项目管理软件推荐,项目管理提效的20个工具和技巧
- 2024年开源项目管理软件有哪些?推荐5款好用的项目管理工具
- 项目管理软件有哪些?推荐7款超好用的项目管理工具
- 项目管理软件哪个最好用?盘点推荐5款好用的项目管理工具
- 项目管理软件有哪些最好用?推荐6款好用的项目管理工具
- 项目管理软件有哪些,盘点推荐国内外超好用的7款项目管理工具
- 2024项目管理软件排行榜(10类常用的项目管理工具全推荐)
- 项目管理软件排行榜:2024年项目经理必备5款开源项目管理软件汇总
- 2024年常用的项目管理软件有哪些?推荐这10款国内外好用的项目管理工具
- 项目管理必备:盘点2024年13款好用的项目管理软件