linux上的缓冲异步文件I/O
Posted
技术标签:
【中文标题】linux上的缓冲异步文件I/O【英文标题】:buffered asynchronous file I/O on linux 【发布时间】:2011-08-05 13:35:43 【问题描述】:我正在寻找在 linux 上进行异步文件 I/O 的最有效方法。
POSIX glibc 实现在用户空间中使用线程。
本机 aio 内核 api 仅适用于非缓冲操作,存在用于内核添加对缓冲操作的支持的补丁,但这些补丁已经超过 3 年了,似乎没有人关心将它们集成到主线中。
我发现了许多其他可以允许异步 I/O 的想法、概念和补丁,尽管其中大多数都在 3 年以上的文章中。所有这些在今天的内核中真正可用的是什么?我读过关于 servlet、acall、内核线程的东西以及更多我现在不记得的东西。
在当今内核中进行缓冲异步文件输入/输出的最有效方法是什么?
【问题讨论】:
(2020) 如果您的内核足够新(5.1+),您可以useio_uring
and get good buffered asynchronous file I/O on Linux。
【参考方案1】:
(2021) 如果您的 Linux 内核足够新(至少 5.1,但更新的内核带来了改进),那么io_uring
will be "the most efficient way to do asynchronous file input/output" *。这适用于缓冲和直接 I/O!
在 2019 年内核食谱视频“通过 io_uring 实现更快的 IO”中,io_uring
作者 Jens Axboe demonstrates buffered I/O via io_uring
finishing in almost half the time of synchronous buffered I/O。正如@Marenz 所指出的,除非您想要用户空间线程io_uring
是唯一 进行缓冲异步I/O 的方式,因为Linux AIO (aka libaio
/io_submit()
) doesn't have the ability to always do buffered asynchronous I/O...
此外,在“现代存储速度非常快”一文中。 Glauber Costa demonstrates how careful use of io_uring
with asynchronous direct I/O can improve throughput compared to using io_uring
for asynchronous buffered I/O 在傲腾设备上。它要求 Glauber 具有用户空间预读实现(没有它缓冲 I/O 显然是赢家),但改进令人印象深刻。
* 这个答案的上下文显然与存储有关(在提到缓冲这个词之后)。对于网络 I/O,io_uring
在后来的内核中稳步改进,以至于它可以与epoll()
之类的东西进行交易,如果它继续下去,总有一天它会在所有情况下都相等或更好。
【讨论】:
【参考方案2】:我不认为异步文件 I/O 的 Linux 内核实现真的可用,除非你也使用 O_DIRECT,抱歉。
这里有更多关于世界当前状态的信息:https://github.com/littledan/linux-aio。它是由曾经在 Google 工作的人在 2012 年更新的。
【讨论】:
【参考方案3】:这些材料看起来很旧——嗯,它是旧的——因为它已经存在很长时间了,虽然绝不是微不足道的,但很好理解。一个你可以举起的解决方案发表在 W. Richard Stevens 的精湛而无与伦比的书中(阅读“圣经”)。这本书是清晰、简洁、完整的稀世珍宝:每一页都赋予了真实而直接的价值:
Advanced Programming in the UNIX Environment
另外两本,也是 Stevens 的,是他的 Unix Network Programming 合集的前两卷:
Volume 1: The Sockets Networking API (与 Fenner 和 Rudoff) 和 Volume 2: Interprocess Communications
我无法想象没有这三本基础书籍;当我找到一个没听说过他们的人时,我傻眼了。
还有更多史蒂文的书,同样珍贵:
TCP/IP Illustrated, Vol. 1: The Protocols
【讨论】:
我们这里有 W. Richard Stevens、Bill Fenner 和 Andrew M. Rudoff 的 UNiX Network-Programming。 (是的,我实际上就这件事咨询过它;)听起来也是个不错的主意,让你提到的那些也 @Marenz - 是的,这就是我上面提到的第 1 卷。最重要的是第一个,Unix Env 中的高级编程。有了这本书,您轻松、舒适和满意的新编程生活将会令您惊喜和高兴:-)【参考方案4】:除非您想编写自己的 IO 线程池,否则 glibc 实现是一个可以接受的解决方案。对于完全在用户空间运行的东西,它实际上工作得非常好。
根据我的经验,内核实现根本不适用于缓冲 IO(尽管我看到其他人说相反的说法!)。如果您想通过 DMA 读取大量数据,这很好,但如果您打算利用缓冲区缓存,这当然会浪费大量时间。 另请注意,内核 AIO 调用实际上可能会阻塞。有一个大小有限的命令缓冲区,大的读取被分解成几个较小的。一旦队列满了,异步命令就会同步运行。惊喜。一两年前我遇到过这个问题,但找不到解释。四处询问给了我“是的,当然,这就是它的工作原理”的答案。 据我了解,支持缓冲 aio 的“官方”兴趣也不是很大,尽管多年来似乎有几种可行的解决方案。我读过的一些论点是“你无论如何都不想使用缓冲区”和“没有人需要那个”和“大多数人甚至还没有使用 epoll”。所以,嗯……嗯。
直到最近,才能通过已完成的异步操作获得 epoll
信号是另一个问题,但与此同时,通过 eventfd
可以正常工作。
请注意,glibc 实现实际上会在__aio_enqueue_request
内按需生成 线程。这可能没什么大不了的,因为生成线程不再那么非常昂贵,但应该意识到这一点。如果您对启动异步操作的理解是“立即返回”,那么这个假设可能不正确,因为它可能首先产生了一些线程。
编辑: 作为旁注,在 Windows 下存在与 glibc AIO 实现中的情况非常相似的情况,其中“立即返回”对异步操作进行排队的假设是不正确的。 如果您要读取的所有数据都在缓冲区缓存中,Windows 将决定改为运行请求同步,因为无论如何它都会立即完成。这是有据可查的,当然听起来也很棒。除非有几兆字节要复制,或者另一个线程有页面错误或同时执行 IO(因此竞争锁)“立即”可能是一个令人惊讶的长时间——我已经看到“立即”时间为 2 -5 毫秒。这在大多数情况下都没有问题,但例如在 16.66 毫秒帧时间的约束下,您可能不想冒险在随机时间阻塞 5 毫秒。因此,“可以从我的渲染线程执行异步 IO 没问题,因为异步不会阻塞”的幼稚假设是有缺陷的。
【讨论】:
@Damon -- 一个漂亮的答案,谢谢!当您说“支持缓冲 aio 的‘官方’兴趣不是特别大”时,这似乎很好地说明了 aio 并没有增加太多,无论用户程序多么想执行 aio,内核在很大程度上无视他,只是做它想做的事。 用户态实现的明显问题是内核必须进行不合格的猜测(它没有关于意图的信息),因此正确的内核实现肯定会非常有益。另一方面,不妥协的自下而上的适当实施当然不是一件容易的事,可能不情愿来自这个事实。工作量/麻烦可能似乎并不值得。尽管在我看来,即使是现有的解决方案(建立在上面,并且做出一些妥协)也可以为许多应用程序添加很多东西。 在我看来,最干净的解决方案是一起移除阻塞 IO 并使所有 IO 异步。然后需要在库中重新实现“通常”的阻塞 IO。 这应该是怎么做的——也许是计划做的——首先,因为,AFAICS,同步输入的唯一可能原因是确保用户程序知道读取物理介质上的索引。 内核的 aio api 确实可以处理缓冲输入,只是它在 io_submit 处被阻塞。诚然,我没有检查缓冲区的结果,可能还有一些返回值。我想我要走的路是将glibc的posix api与线程/回调通知一起使用,并使用eventfd将通知转换为epoll事件。感谢您详细的回答。以上是关于linux上的缓冲异步文件I/O的主要内容,如果未能解决你的问题,请参考以下文章