异步IO实际上发生了什么?

5

我一直在阅读有关异步 IO 优于同步 IO 的原因,因为在异步 IO 中,您的程序可以继续运行,而在同步 IO 中,您需要等待操作完成才能继续进行。
但我不理解这种说法,因为使用同步 IO(例如 write()),内核会将数据写入磁盘 - 它不会自行发生。内核确实需要 CPU 时间来执行它。
因此,在异步 IO 中也需要 CPU 时间,这可能会导致从我的应用程序到内核的上下文切换。因此它并不是真正意义上的阻塞,但是需要 cpu 周期来运行此操作。

  • 这是正确的吗?
  • 这两者之间的区别是什么?我们假设磁盘访问很慢,所以在同步 IO 中,您需要等待数据被写入磁盘,而在异步 IO 中,您等待数据被写入磁盘的时间可以用于继续执行应用程序处理,内核部分写入磁盘的时间很短?
  • 假设我有一个只获取信息并将其写入文件的应用程序。使用异步 IO 而不是同步 IO 是否有任何好处?

同步 IO 的示例:

  • write()

异步 IO 的示例:

  • io_uring(据我所知还具有零拷贝,因此这是一个好处)
  • spdk(应该是最好的,尽管我不知道如何使用它)
  • aio

4
内核将数据写入磁盘 - 这不是自动发生的。实际上,内核向磁盘控制器发送一个消息,然后磁盘控制器会自己完成实际的写入操作,无需处理器或操作系统的帮助。支持DMA的现代磁盘接口将自行获取系统RAM中的数据 - 内核只会向磁盘控制器提供数据的地址(类似指针,但是物理地址而非虚拟地址)。 - Ben Voigt
2
是的,使用异步输出系统完成的工作总量通常至少与同步输出相同。然而,用户往往对用户界面停顿了一小部分时间抱怨得更多,而不是对CPU负载的抱怨。 如果用户界面在响应某些次要用户操作时定期写入文件,同步输出将意味着用户界面会一直停顿或挂起,而文件正在被写入。 异步输出允许程序继续响应用户,即使输出未完全完成。 - Peter
2
磁盘是速度较慢的部分,而不是 CPU。因此,如果您有其他事情要做,请使用异步 I/O。如果您无论如何都要等待,那么就没有理由这样做。 - stark
2
当你执行write操作时,你只需要等待数据被写入内核缓存中,而不是等到它被写入磁盘。数据写入磁盘的过程会在write返回后异步进行。如果你想要完全同步的写入,你需要使用fsync - Chris Dodd
5个回答

3
你的理解部分正确,但你使用哪些工具取决于你喜欢的编程模型,不确定你的程序是否会在等待I/O操作完成时冻结。对于某些专业、高负载的应用程序,有些模型可能略微或中等更有效率,但除非你处于这种情况下,否则你应该选择使编写和维护程序变得简单并且可以移植到您和您的用户关心的系统的模型,而不是某个人营销为高性能的模型。
传统上,有两种无阻塞I/O的方法:
1.将程序构建为事件循环执行select (现在是poll; select已过时且存在重大缺陷),针对一组文件描述符进行操作,这些描述符可能准备好读取输入或接受输出。这需要为尚未准备处理的部分输入以及尚未能够写出的挂起输出保留某种显式状态。
2.将I/O分离成单独的执行上下文。历史上Unix方式是分离进程,并且当你有其他原因想要分离进程时,这仍然是有道理的(特权隔离等),但更现代的方法是使用线程。对于每个I/O通道的单独执行上下文,您只需使用正常的阻塞read/write(甚至是缓冲的stdio函数),任何部分输入或未完成的输出状态都隐含地保存在其执行上下文的调用帧堆栈/局部变量中。
请注意,以上两种选项中,仅后一种选项有助于解决由于磁盘访问缓慢而引起的停顿,因为普通文件始终根据select/poll准备好输入和输出。
现在有一个趋势,可能主要归功于像JavaScript这样的语言,朝向第三种方法,即“异步模型”,使用事件处理程序回调。我发现它更难以处理,需要更多的样板代码,并且比以上两种方法更难以理解,但是很多人喜欢它。如果您想使用它,则最好使用一个抽象出你所提到的Linuxisms(io_uring等)的库,使您的程序可以在其他系统上运行,不依赖于最新的Linux潮流。
现在回答你的具体问题:
假设我有一个应用程序,它所做的所有事情就是获取信息并将其写入文件。与同步I/O相比,使用异步I/O有什么好处吗?
如果您的应用程序只有单个输入源(没有交互性)和单个输出,例如大多数Unix命令,则无论使用哪种编程模型(事件循环、线程、异步回调等),任何类型的异步I/O都没有好处。最简单和最有效的方法就是读取和写入。

我认为这种趋势不是由Javascript引起的。Win32在Javascript之前就有异步I/O,我猜它是从VMS那里得来的。 - MSalters

2

内核确实需要CPU时间才能完成操作。

这是正确的吗?

基本上是的。

这两者之间的区别在于我们假设磁盘访问速度很慢...在异步IO中,等待写入磁盘的时间可以用来继续进行应用程序处理,而内核写入磁盘的部分很小吗?

完全正确。

假设我有一个应用程序,它所做的就是获取信息并将其写入文件。使用异步IO是否有任何好处?

这取决于许多因素。该应用程序如何“获取信息”?它是否需要大量CPU?它是否使用与写入相同的IO?它是否是处理多个请求的服务?同时连接了多少个?首先性能是否重要?在某些情况下:使用异步IO可能会带来显着的好处。在其他一些情况下,通过在单独的线程中使用同步IO可以获得大部分好处。在其他情况下,单线程同步IO可能已经足够。


2
我不理解这个说法,因为使用同步IO(如write()),内核将数据写入磁盘 - 它不会自动发生。内核需要CPU时间才能执行它。
不是的。大多数现代设备都能够通过自己(使用DMA或总线主控)传输数据到/从RAM中。
例如; CPU可能会告诉磁盘控制器“将4个扇区读入地址为0x12345000的RAM中”,然后CPU可以做任何它喜欢的事情,而磁盘控制器则进行传输(并在磁盘控制器完成数据传输时通过IRQ中断)。
但是,在现代系统中(其中您可以有任意数量的进程都想同时使用同一设备),设备驱动程序必须维护一个待处理操作列表。在这种情况下(在负载下),当设备生成IRQ以表示它完成了一个操作时,设备驱动程序通过告诉设备开始下一个“待处理操作”来响应。这样,设备几乎不会闲置等待被要求启动下一个操作(更好的设备利用率),而CPU几乎所有时间都在做其他事情(在IRQ之间)。
当然,通常硬件更先进(例如,具有内部操作队列,因此驱动程序可以告诉它执行多个操作,并且在完成上一个操作后,它可以立即开始下一个操作);而且通常驱动程序更先进(例如,具有“IO优先级”,以确保首先完成更重要的事情,而不仅仅是具有简单的待处理操作FIFO队列)。
假设我有一个应用程序,它所做的就是获取信息并将其写入文件。使用异步IO是否有任何好处?
假设你从deviceA获取信息(同时CPU和deviceB空闲);然后稍微处理一下这些信息(同时deviceA和deviceB空闲);然后将结果写入deviceB(同时deviceA和CPU空闲)。您可以看到大多数硬件大部分时间都没有做任何事情(利用率低)。
使用异步IO的话,当设备A正在获取下一组信息时,CPU可以处理当前组信息,而设备B则可以写入上一组信息。在理想情况下(没有速度不匹配),您可以实现100%的利用率(设备A、CPU和设备B从未空闲);即使存在速度不匹配(例如,设备B需要等待CPU完成当前任务的处理),任何东西空闲的时间也将被最小化(并尽可能地最大化利用率)。
另一个选择是使用多个任务-例如,一个任务同步从设备A获取数据,并在读取数据后通知另一个任务;第二个任务等待数据到达并处理它,并在处理完成后通知另一个任务;然后第三个任务等待数据处理完成后同步将其写入设备B。对于利用率来说,这与使用异步IO的效果基本相同(事实上,它可以被认为是“模拟异步IO”)。问题在于,您增加了管理和同步多个任务所需的额外开销(状态和堆栈消耗更多RAM,任务切换,锁竞争等),使代码更复杂且更难以维护。

谢谢。问题是我在“top”上看到了这个:%Cpu10 : 0.0 us, 99.7 sy, 0.0 ni, 0.3 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st。我有一个nvme磁盘。我没有看到它的wa(等待)。这是否意味着我写入效率低下?我记得使用perf对我的应用程序进行分析,我看到大部分周期都进入内核,并使用诸如~copy 64(我不记得确切名称)之类的函数。 - hudac
1
@hudac:对于高级文件IO,情况更加复杂,因为有缓存存在(主要使用“否则空闲的RAM”)。写入通常只是缓冲,以便它们看起来快速/即时(因为数据实际上直到内核稍后才开始写入设备),当读取的文件/数据已经缓存在RAM中时,读取也可以看起来快速/即时。但是... - Brendan
@hudac:如果你写入的数据超过了操作系统的缓冲区,或者读取的数据超过了操作系统的缓存(例如因为“否则空闲的RAM”非常少和/或其他进程正在执行大量任务和/或你正在读取/写入大量数据),或者你需要保证你的数据实际上已经写入磁盘(并且如果有电源故障等情况不会丢失);那么缓冲/缓存就无法帮助你。 - Brendan
1
@hudac:“先做A,再做B,然后做C”永远不如“并行地做A、B和C”更好(对于性能和利用率);因此同步IO永远不如异步IO更好(对于性能和利用率)。它们只在极不可能的情况下“相等”(例如:涉及不存在的设备和/或少量数据的非常简单的场景,使用单个CPU,而没有其他进程执行任何操作)。例如:如果获取是CPU密集型的,并且处理获取的数据也是CPU密集型的,则(实际上)您可能拥有4个或更多个CPU,并且可以同时执行两者。 - Brendan
1
@hudac:同步IO的主要好处在于它更容易让程序员理解,因为它符合“过程式思维”。具体来说,“A发生,然后B发生,然后C发生”比“所有这些事情可以同时发生,我无法知道任何事情将以什么顺序完成”更容易处理。 - Brendan
显示剩余3条评论

1
在任何情况下,上下文切换都是必要的。内核始终在其自己的上下文中工作。因此,同步访问并不能节省处理器时间。通常,写入不需要太多的处理器工作。限制因素是磁盘响应。问题是我们是否会等待这个响应完成我们的工作。
假设我有一个应用程序,它所做的就是获取信息并将其写入文件。使用异步IO与同步IO相比,是否有任何好处?
如果您实现同步访问,您的顺序如下:
1. 获取信息 2. 写入信息 3. 转到 1.
因此,在write()完成之前,您无法获取信息。假设信息提供者像您写入的磁盘一样慢。在这种情况下,该程序将比异步程序慢两倍。如果信息提供者不能等待并在您写入时保存信息,则会丢失部分信息。这些信息源的示例可能是快速进程的传感器。在这种情况下,您应该同步读取传感器并异步保存获得的值。

1
异步IO并不比同步IO更好,反之亦然。问题在于哪种对于您的用例更好。
同步IO通常编码更简单,但异步IO可以在更复杂的代码的代价下提高吞吐量和响应能力。
我从来没有因为文件访问而从异步IO中获得任何好处,但一些应用程序可能会从中受益。
访问“缓慢”的IO(如网络或终端)的应用程序受益最大。使用异步IO允许它们在等待IO完成时执行有用的工作。这意味着能够为更多客户提供服务或保持应用程序对用户响应能力。
(而“缓慢”只是指IO操作完成的时间是无限的,它可能永远不会完成,例如等待用户按下回车键或网络客户端发送命令)
最后,异步IO并不做更少的工作,它只是在时间上分布不同以减少空闲等待。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接