使用 FileInputStream 时如何确定理想的缓冲区大小?

我有一个从文件创建 MessageDigest (散列)的方法,我需要对很多文件(> = 100,000)执行此操作。我应该使用多大的缓冲区来读取文件,以最大限度地提高性能?

大多数人都熟悉基本代码(为了以防万一,我在这里重复一下) :

MessageDigest md = MessageDigest.getInstance( "SHA" );
FileInputStream ios = new FileInputStream( "myfile.bmp" );
byte[] buffer = new byte[4 * 1024]; // what should this value be?
int read = 0;
while( ( read = ios.read( buffer ) ) > 0 )
md.update( buffer, 0, read );
ios.close();
md.digest();

最大化吞吐量的理想缓冲区大小是多少?我知道这是系统依赖的,我很确定它的操作系统,文件系统,还有硬盘驱动器依赖,也许还有其他硬件/软件的混合。

(我应该指出,我对 Java 有点新,所以这可能只是一些我不知道的 JavaAPI 调用。)

编辑: 我事先并不知道这将用于哪些类型的系统,所以我不能假设很多。(出于这个原因,我使用了 Java。)

编辑: 上面的代码缺少类似 try. . catch 这样的东西,这样文章就变小了

125526 次浏览

是的,它可能取决于各种各样的事情——但我怀疑它会有很大的不同。我倾向于选择16K 或32K 作为内存使用和性能之间的良好平衡。

注意,在代码中应该有一个 try/finally 块,以确保即使抛出异常也关闭流。

在理想情况下,我们应该有足够的内存来在一次读操作中读取文件。 这将是最好的表现,因为我们让系统管理文件系统,分配单位和硬盘驱动器随意。 在实践中,您很幸运能够提前知道文件大小,只需使用四舍五入到4K 的平均文件大小(NTFS 上的默认分配单元)。 最棒的是: 创建一个测试多个选项的基准。

您可以使用 BufferedStreams/reader,然后使用它们的缓冲区大小。

我相信 BufferedXStreams 使用8192作为缓冲区大小,但是正如 Ovidiu 所说,您可能应该对一大堆选项运行一个测试。它的最佳大小实际上取决于文件系统和磁盘配置。

在大多数情况下,这并不重要。只要选择一个好的大小,如4K 或16K,并坚持它。如果 确定认为这是应用程序中的瓶颈,那么应该开始分析以找到最佳缓冲区大小。如果您选择的大小太小,您将浪费时间执行额外的 I/O 操作和额外的函数调用。如果您选择的大小太大,您将开始看到许多缓存丢失,这将真正减慢您的速度。不要使用大于 L2缓存大小的缓冲区。

正如在其他答案中已经提到的,使用 BufferedInputStreams。

在此之后,我想缓冲区大小并不真正重要。要么程序是 I/O 绑定的,并且在 BIS 缺省情况下增加缓冲区大小,将不会对性能产生任何大的影响。

或者程序在 MessageDigest.update ()中是 CPU 绑定的,并且大部分时间没有花在应用程序代码中,因此调整它不会有任何帮助。

(嗯... 使用多个核心,线程可能会有所帮助。)

使用 JavaNIO 的 FileChannel 和 MappedByteBuffer 读取文件很可能会产生一个比任何涉及 FileInputStream 的解决方案都要快得多的解决方案。基本上,内存映射大文件,并对小文件使用直接缓冲区。

最佳缓冲区大小与许多因素有关: 文件系统块大小、 CPU 缓存大小和缓存延迟。

大多数文件系统都配置为使用块大小为4096或8192。理论上,如果你配置你的缓冲区大小,所以你读取的数据比磁盘块多几个字节,那么文件系统的操作可能会非常低效(例如,如果你配置你的缓冲区为一次读取4100个字节,每次读取需要文件系统读取2个块)。如果块已经在缓存中,那么您将付出 RAM-> L3/L2缓存延迟的代价。如果运气不好,块还没有进入缓存,那么您也要为磁盘-> RAM 延迟付出代价。

这就是为什么大多数缓冲区的大小是2的幂,通常大于(或等于)磁盘块的大小。这意味着您的一个流读取可能导致多个磁盘块读取-但是这些读取将始终使用一个完整的块-没有浪费的读取。

现在,在一个典型的流场景中,这是一个相当大的偏移,因为当你点击下一次读取时,从磁盘读取的块仍然在内存中(毕竟我们在这里做的是顺序读取)-所以你在下一次读取时付出了 RAM-> L3/L2缓存延迟的代价,而不是磁盘-> RAM 延迟。就数量级而言,磁盘-> 内存延迟非常缓慢,几乎可以淹没任何其他延迟,你可能正在处理。

因此,我怀疑如果您运行一个缓存大小不同的测试(我自己还没有这样做) ,您可能会发现缓存大小对文件系统块的大小有很大的影响。除此之外,我怀疑事情会很快趋于平稳。

这里有一个 的条件和异常-系统的复杂性实际上是相当惊人的(仅仅获得一个处理 L3-> L2缓存传输是令人难以置信的复杂,它随着每个 CPU 类型的变化)。

这导致了“现实世界”的答案: 如果你的应用程序大约有99% ,设置缓存大小为8192,然后继续(更好的是,选择封装而不是性能,并使用 BufferedInputStream 来隐藏细节)。如果有1% 的应用程序高度依赖磁盘吞吐量,那么你可以精心设计你的实现,这样你就可以交换不同的磁盘交互策略,并提供旋钮和刻度盘,让你的用户可以测试和优化(或者提出一些自我优化的系统)。

1024适合于各种各样的情况,尽管在实践中,您可能会看到使用更大或更小的缓冲区大小会带来更好的性能。

这将取决于许多因素,包括文件系统块 大小和中央处理器硬件。

对于缓冲区大小选择2的幂也很常见,因为大多数底层 硬件的结构由文件块和缓存大小的2次方组成 类允许您在构造函数中指定缓冲区大小 使用默认值,在大多数 JVM 中为2的幂。

无论选择哪种缓冲区大小,性能提高都是最大的 正在从非缓冲文件访问移动到缓冲文件访问。调整缓冲区大小可能 稍微提高性能,但是除非你使用一个非常小的或者非常小的 缓冲区大,不太可能产生重大影响。

在 BufferedInputStream 的源代码中,你会发现: private static int DEFAULT _ BUFFER _ SIZE = 8192;
所以可以使用默认值。
但是如果你能找出更多的信息,你就会得到更有价值的答案。
例如,您的 adsl 可能更喜欢1454字节的缓冲区,这是因为 TCP/IP 的有效负载。对于磁盘,可以使用与磁盘块大小匹配的值。