【问题标题】:asynchronous IO io_submit latency in Ubuntu LinuxUbuntu Linux 中的异步 IO io_submit 延迟
【发布时间】:2016-04-06 23:14:19
【问题描述】:

我正在寻找有关如何为我在 Ubuntu Linux 14.04 上运行的应用程序获得高效和高性能异步 IO 的建议。

我的应用程序处理事务并在磁盘/闪存上创建一个文件。随着应用程序通过交易进行,会创建额外的块,这些块必须附加到磁盘/闪存上的文件中。该应用程序还需要在处理新事务时经常读取此文件的块。除了创建一个必须附加到该文件的新块之外,每个事务可能还需要从此文件中读取不同的块。有一个传入的事务队列,应用程序可以继续处理队列中的事务,以创建足够深的 IO 操作管道,以隐藏磁盘或闪存上读取访问或写入完成的延迟。对于尚未写入磁盘/闪存的块(由上一个事务放入写入队列)的读取,应用程序将停止,直到相应的写入完成。

我有一个重要的性能目标 - 应用程序应该产生尽可能低的延迟来发出 IO 操作。我的应用程序需要大约 10 微秒来处理每个事务,并准备好对磁盘/闪存上的文件进行写入或读取。发出异步读取或写入的额外延迟应尽可能小,以便应用程序可以在只需要文件写入时以每个事务尽可能接近 10 微秒的速率完成处理每个事务。

我们正在试验一种使用 io_submit 发出写入和读取请求的实现。对于满足我们要求的最佳方法的任何建议或反馈,我将不胜感激。 io_submit 是否会给我们最好的性能来实现我们的目标?我应该对每次写入 io_submit 的延迟和每次读取 io_submit 的延迟有什么期望?

使用我们的实验代码(在 2.3 GHz Haswell Macbook Pro、Ubuntu Linux 14.04 上运行),我们测量了在扩展输出文件时写入 io_submit 大约 50 微秒。这太长了,我们甚至没有接近我们的性能要求。任何帮助我以最短延迟启动写入请求的指导将不胜感激。

【问题讨论】:

  • 为什么不读写一个确定长度的内存缓冲区呢?这个缓冲区应该能够容纳足够的数据量,以便一旦缓冲区已满,90% 的读/写应该在内存中提供(无磁盘访问),将其刷新到磁盘(后台作业)或者如果你想保存上面所有的问题都可以用在内存数据库中,比如 aerospike 或 redis 等。
  • 我们不期望时间局部性,因此缓存无助于读取。输出文件大小将远大于可用内存。首先写入内存缓冲区可以帮助减少写入 IOP 的数量,但它带来了确保非易失性存储持久性等其他复杂性。如果我能得到一些与 io_submit 调用延迟相关的问题的答案,那就太好了.

标签: linux performance latency aio


【解决方案1】:

Linux AIO(有时称为 KAIO 或 libaio)是一种黑魔法,经验丰富的从业者知道其中的陷阱,但出于某种原因,它是 taboo to tell someone about gotchas they don't already know。通过在网上摸索和经验,我想出了一些例子,其中 Linux 的 异步 I/O 通过io_submit() 提交可能会(默默地)同步,从而将其变成阻塞(即不再快速)调用:

  1. 您正在提交缓冲(也称为非直接)I/O。您受 Linux 缓存的支配,并且您的提交可以在以下情况下同步:
    • 您正在阅读的内容尚未在“读取缓存”中。
    • “写缓存”已满,在完成某些现有写回之前无法接受新的写请求。
  2. 您要求对文件系统中的文件进行直接 I/O,但无论出于何种原因,文件系统决定忽略 O_DIRECT“提示”(例如,您提交 I/O 的方式不符合 O_DIRECT对齐约束,文件系统或特定文件系统的配置不​​支持O_DIRECT),它选择silently perform buffered I/O instead,导致上述情况。
  3. 您正在对文件系统中的文件进行直接 I/O,但文件系统必须执行同步操作(例如通过写回读取元数据/更新元数据)才能满足您的 I/O。一个常见的例子是发出“分配写入”(例如,因为您正在附加/扩展文件的末尾或填充未分配的孔),这听起来像提问者正在做的事情(“附加到文件”) .一些filesystems such as XFS try harder to provide good AIO behaviour,但即使在那里,用户也必须小心避免将某些操作并行发送到文件系统,否则io_submit() 将在另一个操作完成时再次变成阻塞调用。 Seastar framework contains a small lookup table of filesystem specific cases
  4. 您提交的未完成 I/O 过多。您的磁盘/磁盘控制器将具有可同时处理的最大 I/O 请求数,并且每个特定设备都有最大请求队列大小(请参阅/sys/block/[disk]/queue/nr_requests documentation 和未记录的/sys/block/[disk]/device/queue_depth ) 在内核中。制作I/O requests back-up and exceed the size of the kernel queues leads to blocking
  5. Linux 块设备堆栈中请求和提交到磁盘之间的层必须阻塞。例如,在更新单个磁盘上的 RAID 1 元数据时,Linux 软件 RAID (md) 之类的东西可能会使通过它的 I/O 请求停止。
  6. 您的提交导致内核等待,因为:
    • 它需要获取一个正在使用的特定锁(例如i_rwsem)。
    • 它需要分配一些额外的内存或分页。
  7. 您将 I/O 提交给一个不是“常规”文件或块设备的文件描述符(例如,您的描述符是管道或套接字)。

以上列表并非详尽。

对于 >= 4.14 内核,RWF_NONBLOCK flag 可用于使上述一些阻塞场景产生噪音。例如,当使用缓冲并尝试读取尚未在页面缓存中的数据时,RWF_NONBLOCK flag will cause submission to fail with EAGAIN when blocking would otherwise occur.显然,您仍然 a) 需要支持此标志的 4.14(或更高版本)内核,并且 b) 必须了解它未涵盖的情况。我注意到有patches that have been accepted or are being proposed to return EAGAIN in more scenarios that would otherwise block,但在撰写本文时(2019)RWF_NONBLOCK is not supported for buffered filesystem writes

替代方案

如果您的内核 >=5.1,您可以尝试使用 io_uring,它在不阻止提交方面做得更好(这是一个完全不同的界面,是 2020 年的新界面)。

参考文献

相关:

希望这篇文章能对某人有所帮助(如果对你有帮助,你可以投票吗?谢谢!)。

【讨论】:

    【解决方案2】:

    我在这里以proposed Boost.AFIO 的作者身份发言。

    首先,Linux KAIO (io_submit) 几乎总是阻塞,除非 O_DIRECT 开启并且不需要分配范围,如果 O_DIRECT 开启,您需要在 4Kb 对齐边界上读取和写入 4Kb 倍数,否则您强制设备做一个读-修改-写。因此,除非您将应用程序重新架构为 O_DIRECT 和 4Kb 对齐的 i/o 友好,否则使用 Linux KAIO 将一无所获。

    其次,永远不要在写入期间扩展输出文件,您会强制进行扩展区分配,并且可能会强制执行元数据刷新。而是将文件的最大范围分配到某个适当大的值,并保留文件末尾的内部原子计数器。这应该将问题减少到仅对 ext4 是批处理和惰性的范围分配的问题 - 更重要的是,您不会强制元数据刷新。这应该意味着 ext4 上的 KAIO 大部分时间都是异步的,但意外地会同步,因为它会将延迟的分配刷新到日志中。

    第三,我可能会解决您的问题的方法是使用原子追加 (O_APPEND) 而不使用 O_DIRECT 或 O_SYNC,因此您所做的是将更新附加到内核页面缓存中不断增长的文件中,这非常快速且并发安全的。然后,您不时地垃圾收集日志文件中的哪些数据是陈旧的,以及可以使用 fallocate(FALLOC_FL_PUNCH_HOLE) 解除分配的范围,因此物理存储不会永远增长。这将合并写入存储的问题推到了内核上,在内核中已经花费了很多精力来加快速度,并且因为它是一个始终向前推进的写入,您将看到写入以与写入它们的顺序相当接近的顺序到达物理存储使功率损耗恢复简单。后一种选择是数据库如何做到这一点,实际上是日志文件系统如何做到这一点,尽管可能需要对您的软件进行大量重新设计,但您需要执行此算法已被证明是通用问题案例中延迟与持久性的最佳平衡。

    如果上述所有工作似乎都需要做很多工作,操作系统已经提供了所有三种技术,这些技术整合到一个高度优化的实现中,也就是众所周知的内存映射:4Kb 对齐 i/o、O_DIRECT,从不扩展文件,所有异步 i/o。在 64 位系统上,只需将文件分配到非常大的数量并将其映射到内存中。随心所欲地阅读和写作。如果您的 i/o 模式混淆了可能发生的内核页面算法,您可能需要在各处使用 madvise() 来鼓励更好的行为。 madvise() 少即是多,相信我。

    很多人尝试使用各种 O_DIRECT 算法复制 mmap,却没有意识到 mmap 已经可以满足您的所有需求。我建议先探索这些,如果 Linux 不能正常运行,请尝试 FreeBSD,它具有更可预测的文件 i/o 模型,然后才深入研究滚动您自己的 i/o 解决方案的领域。作为一个整天都在做这些的人,我强烈建议你尽可能避免使用它们,文件系统是古怪和怪异行为的恶魔坑。将永无止境的调试留给其他人。

    【讨论】:

    • 非常感谢您提供的信息丰富的回复。我需要一些时间来消化您的建议并进行实验。如果我有后续问题,我会在这里回复。
    • 还可以查看@Arvid 在 SO 上关于 Linux KAIO 的任何帖子,他与 SO 的权威非常接近。他就是写blog.libtorrent.org/2012/10/asynchronous-disk-io的那个人
    • 嗯。 mmap 不会在首次写入时引入额外的延迟(页面错误 + 查找空闲页面 + 映射到进程 VM 空间)?如果我想跟上高带宽的数据流,这真的是扩展文件的最佳方式吗?目前这对我来说不是一个假设的问题:)。我认为,我真正想要的是预先分配一个双缓冲区并重叠填充一个并将另一个刷新到磁盘...
    • 通过正确地戳内核,您可以创建一个映射,该映射在所有进程中自动扩展为一个地址空间预留。 LLFIO 进行了正确的戳,并且在支持所述戳的系统上运行良好。这并不是说双缓冲可能不适合您的问题,它实际上取决于您的存储、您的问题域等。但请始终先给 mmaps 一个机会。他们总是从开发者那里得到调优和喜爱。 O_DIRECT 之类的其他东西可能不会。
    • Linux 在 2008 年的 mmap 相当慢。最近的 Linux 有一个惊人的 mmap,它经常让我惊讶它是多么快速和恒定的时间。也就是说,Linus 没有错,TLB 击落总是很昂贵的。 mmaps 基本上让一个人在冷代码中付出大量成本,以换取通常在热代码中的最低成本,如果你做对了一切。但是,使用 mmaps 比您自己的自定义用户空间 O_DIRECT 文件系统算法更容易使一切正常。这些很难做到正确,并且在任何任意存储上都表现出色。内核为您完成所有调整和调试!
    猜你喜欢
    • 2023-03-31
    • 2013-10-25
    • 2016-02-02
    • 2011-04-23
    • 1970-01-01
    • 2019-03-13
    • 2018-08-01
    • 2018-06-13
    • 2014-07-08
    相关资源
    最近更新 更多