为什么使用缓冲区来读/写Streams

时间:2010-05-12 11:55:57

标签: .net language-agnostic stream

在阅读有关阅读和编写Streams的各种问题之后,所有各种答案都将这样的内容定义为正确的方法:

private void CopyStream(Stream input, Stream output)
{
   byte[] buffer = new byte[16 * 1024];
   int read;
   while ((read = input.Read(buffer, 0, buffer.Length)) > 0)
   {
      output.Write(buffer, 0, read);
   } 
}

两个问题:

为什么要读写这些较小的块?

使用缓冲区大小的意义是什么?

2 个答案:

答案 0 :(得分:6)

如果你一次读取一个字节,那么你调用的每个字节都有调用函数来读取字节的开销,以及额外的开销(例如,做一个fileposition += 1来记住你在文件中的位置是,检查你是否已到达文件的末尾,等等)

如果你读了4000个字节,那么你有相同的开销(在上面的例子中,1个函数调用,一个add(fileposition + = 4000),并且一个检查你是否在文件的末尾。所以在开销方面,你的速度提高了4000倍。(实际上,还有其他成本,所以你不会看到那么大的收益,但你已经大幅减少了开销)

当然,您可以创建一个与整个文件一样大的缓冲区,并获得绝对最小的开销。但是:

  • 文件可能很大 - 比程序可用的内存大,所以这只会失败。或者它可能是如此之大,以至于你开始使用虚拟内存,这将大大减慢速度。因此,将其分解为较小的块意味着您可以使用小型固定大小的缓冲区复制无限量的数据

  • 您的操作系统和设备可能同时读写数据(例如从一个物理磁盘驱动器复制到另一个物理磁盘驱动器)。如果在写入所有数据之前读取所有数据,则必须等待整个读取才能开始写入。但在许多情况下,你可能能够并行执行这两个操作 - 所以读取一个小块并开始“异步”写入(在后台),同时返回并读取下一个块。

  • 收益递减。读取4个字节而不是1个字节可能要快4倍。但读取4,000,40,000或400,000不会加快速度(事实上,由于上​​述原因,较大的缓冲区实际上可能会减慢速度)。

  • 在某些情况下,物理设备使用特定的数据大小(例如,每个扇区4096个字节,每个缓存行128个字节,或每个数据包1500个字节,或CPU总线上的8个字节(64位))。将数据划分为与底层传输/存储机制匹配(或是其倍数)的块可以帮助硬件更有效地处理数据。

通常,4kB到128kB之间的I / O缓冲区在大多数情况下效果最佳,您可以将它们调整到正在执行的特定操作,因此没有适合所有情况的“完美”大小。

请注意,在大多数I / O情况下,使用了许多缓冲区。例如从磁盘复制数据时(简单来说),它从磁盘读取到硬盘驱动器中的读取缓存(缓冲区),然后通过接口电缆发送到计算机的驱动器控制器,驱动器控制器也可以缓冲数据。然后它可以通过I / O缓冲区传输到RAM中,直到你的程序准备好接收它(它甚至可能在你要求之前获取数据,因为它希望你继续读取它相同的文件,并尝试缓冲数据,因此您不必等待它)。然后你将它读入缓冲区并编写它。然后它转到另一个I / O缓冲区,被发送到驱动器控制器,传递到驱动器,并缓存在写入缓存中。最终,硬盘驱动器将决定将数据实际存储在其写入缓存中,并且您的副本将完成 - 大部分内容都在后台进行,因此在程序认为已完成写入后的几秒钟内可能无法完成写入。已经完成了另一项任务。 (这就是为什么你必须在拔掉它们之前“安全地移除”USB驱动器 - 操作系统可能还没有实际将所有数据写入设备,即使在计算机说你的复制操作完成后很多秒钟也是如此)

答案 1 :(得分:4)

您通常可以选择要读取和写入的大小。但是,某些值对于特定体系结构将更加优化。在我不知情的情况下,这些是什么。我总是倾向于坚持我喜欢的功能,例如4K(我使用写驱动程序的NT系统上的页面大小)。但是我已经在用户模式中尝试了更大的尺寸,我从未遇到过任何问题。我尽量保持IO调用的数量较少。

我的建议是,如果它的大小(操作开销与获得的数量)或非常大(IO系统阻塞和饱和),那么块大小实际上只是重要的。

我认为对于任何特定情况你都应该

  1. 最小化IO调用次数
  2. 如果真正的性能存在问题,请改变此策略。