缓冲 vs 无缓冲 输入输出(IO)

104

我了解到程序中的I/O默认是有缓存的,也就是说它们会从临时存储中提供给请求的程序。

我明白缓存可以提高IO性能(可能是通过减少系统调用实现)。

我看到过禁用缓存的示例,例如C语言中的setvbuf。那么这两种模式之间有什么区别,应该在何时使用其中一种而不是另一种呢?

2个回答

153

当您想要确保输出已经写入后才继续时,需要无缓冲输出。一个例子是使用C运行时库下的标准错误流 - 这通常默认情况下是无缓冲的。由于错误(希望如此)相对较少,因此您希望立即知道它们的存在。另一方面,标准输出流已缓冲的,因为假定其将传输更多的数据。

另一个例子是日志记录库。如果您的日志消息在进程内被保存在缓冲区中,而且进程崩溃了,那么这个输出有很大的可能永远不会被写入。

此外,不仅是系统调用最小化了,磁盘I / O也是一样。假设程序以每次一个字节的方式读取文件。使用无缓冲输入,即使它可能必须读取整个块(磁盘硬件本身可能具有缓冲区,但您仍然会访问较慢的磁盘控制器),也会每个字节都到磁盘上(相对非常慢)。

通过缓冲,在一次性将整个块读入缓冲区,然后以单个字节从(内存中,速度极快的)缓冲区域传递给您。

请记住,缓冲可以采用许多形式,例如以下示例:

+-------------------+-------------------+
| Process A         | Process B         |
+-------------------+-------------------+
| C runtime library | C runtime library | C RTL buffers
+-------------------+-------------------+
|               OS caches               | Operating system buffers
+---------------------------------------+
|      Disk controller hardware cache   | Disk hardware buffers
+---------------------------------------+
|                   Disk                |
+---------------------------------------+

图表很棒。值得一提的是,FILE对象(流)的内部缓冲区与fgets所需的缓冲区参数完全不同。在我编写了一些代码来弄清楚它之前,这让我困惑了数小时。QAQ - Rick
谁会一个字节一个字节地读取文件呢? - user16217248
@user16217248:可能是这样,但通常你不知道数据的底层来源(或目标)的属性。它是来自标准输入还是文件,是本地文件、网络挂载文件、以每秒300位运行的终端设备,还是连接到另一个可能会间歇性传递数据的进程的管道?UNIX文件模型的强大之处(“一切皆文件”)也使得明确定制I/O策略变得困难。 - paxdiablo
@paxdiablo 一个存储在磁盘上的文件,例如应用程序的配置文件。 - user16217248
@user16217248:配置文件通常足够小,可以完全读入内存并从那里使用(也可能在本地磁盘上,但不保证)。对于这样的情况,我可能会尝试读取10K块,因为它很可能只需要一次读取操作,如果您的配置文件只有20个字节,那么10K并不会浪费太多空间 :-) - paxdiablo
显示剩余4条评论

48

当你有大量字节序列准备写入磁盘并且想要避免在中间进行额外的复制时,你需要无缓冲输出。

缓冲输出流将把写入结果累计到一个中间缓冲区中,仅在累积足够的数据(或者请求flush())时将其发送到操作系统文件系统。这减少了文件系统调用的数量。由于对大多数平台而言,与短memcpy相比,文件系统调用可能很昂贵,因此在执行大量小写入时,缓冲输出是一种净优势。当您已经有大型缓冲区要发送时,无缓冲输出通常更好-- 复制到中间缓冲区不会进一步减少操作系统调用的数量,并引入了额外的工作。

无缓冲输出与确保数据到达磁盘没有任何关系; 这个功能由flush()提供,并且适用于缓冲和无缓冲流。无缓冲IO写入不保证数据已到达物理磁盘--如果操作系统文件系统希望,它可以无限期地保留您的数据的副本,从不将其写入磁盘。只有在调用flush()时,它才需要将其提交到磁盘(请注意,close()将代表您调用flush())。


调用 flush() 方法能够保证数据被写入磁盘吗?我认为它只是将数据传递到磁盘的缓存区。 - jrdioko
2
你需要使用 O_SYNC 来确保写入的保证。 - moshbear
非缓存IO是指直接写入磁盘。因此,术语“非缓存”(没有中间缓冲区,而是直接写入磁盘)适用于WinAPI,您可以使用FILE_FLAG_NO_BUFFERING和FILE_FLAG_WRITE_THROUGH调用CreateFile以确保数据在每次写入后直接持久化。对于其他一些操作系统,我不确定。 - Martin Kosicky

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