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

ARK*_*BAN 147 java filesystems performance file-io buffer

我有一个从文件创建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();
Run Code Online (Sandbox Code Playgroud)

什么是最大化吞吐量的缓冲区的理想大小?我知道这是依赖于系统的,我很确定它的操作系统,文件系统硬盘依赖,并且可能还有其他硬件/软件.

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

编辑:我不提前知道将要使用的系统类型,所以我不能假设很多.(因为那个原因,我正在使用Java.)

编辑:上面的代码缺少像try..catch这样的东西,以使帖子更小

Kev*_*Day 200

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

大多数文件系统都配置为使用4096或8192的块大小.理论上,如果您配置缓冲区大小以便读取比磁盘块多几个字节,则使用文件系统的操作可能效率极低(即,如果您将缓冲区配置为一次读取4100个字节,每次读取将需要文件系统进行2次块读取.如果这些块已经在缓存中,那么你最终会支付RAM的价格 - > L3/L2缓存延迟.如果你运气不好而且这些块还没有在缓存中,那么你也需要支付磁盘 - > RAM延迟的价格.

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

现在,这在典型的流式传输方案中相当偏移,因为当您点击下一次读取时,从磁盘读取的块仍将在内存中(我们在这里执行顺序读取) - 所以你最终在下次读取时支付RAM - > L3/L2缓存延迟价格,但不支持磁盘 - > RAM延迟.就数量级而言,磁盘 - > RAM延迟非常慢,几乎淹没了您可能正在处理的任何其他延迟.

因此,我怀疑如果您运行具有不同高速缓存大小的测试(我自己没有这样做),您可能会发现高速缓存大小的影响大到文件系统块的大小.在此之上,我怀疑事情会很快平稳.

有一的条件和例外这里-该系统的复杂性实际上是相当惊人的(刚开L3手柄- >二级缓存传输是一种精神令人难以置信的复杂,它与每一个CPU类型的变化).

这导致了"真实世界"的答案:如果您的应用程序像99%那样,请将缓存大小设置为8192并继续(更好的是,选择封装而不是性能并使用BufferedInputStream来隐藏细节).如果您在1%的高度依赖磁盘吞吐量的应用程序中,请制定实施方案,以便您可以更换不同的磁盘交互策略,并提供旋钮和拨号以允许您的用户进行测试和优化(或提出一些自我优化系统).

  • 我在手机 (Nexus 5X) 上为我的 Android 应用程序做了一些基准测试:小文件 (3,5Mb) 和大文件 (175 Mb)。并发现黄金大小将是 524288 长度的 byte[]。好吧,如果根据文件大小在小缓冲区 4Kb 和大缓冲区 524Kb 之间切换,你可能会赢得 10-20 毫秒,但这不值得。所以 524 Kb 是我的最佳选择。 (3认同)

Jon*_*eet 17

是的,它可能取决于各种各样的东西 - 但我怀疑它会产生很大的不同.我倾向于选择16K或32K作为内存使用和性能之间的良好平衡.

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

  • @MohammadrezaPanahi:请不要对獾用户使用评论。你在第二条评论前等了*不到一个小时*。请记住,用户很容易睡着,或者在开会,或者基本上*忙于其他事情*并且没有义务回复评论。但是要回答您的问题:这完全取决于上下文。如果您在一个非常受内存限制的系统上运行,您可能需要一个小缓冲区。如果您在大型系统上运行,使用更大的缓冲区将减少读取调用的次数。凯文·戴的回答非常好。 (2认同)

Ada*_*eld 7

在大多数情况下,它确实无关紧要.只需选择一个好的尺寸,如4K或16K,并坚持下去.如果你肯定这是你的应用程序的瓶颈,那么你应该开始分析以找到最佳的缓冲区大小.如果选择的尺寸太小,则会浪费时间进行额外的I/O操作和额外的函数调用.如果你选择一个太大的大小,你会开始看到很多缓存未命中,这将真正减慢你的速度.不要使用大于L2缓存大小的缓冲区.


Ovi*_*rar 5

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


Joh*_*ner 5

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

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


Ale*_*der 5

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


小智 5

在 BufferedInputStream\xe2\x80\x98s 源中你会发现: private static int DEFAULT_BUFFER_SIZE = 8192;
\n所以您可以使用该默认值。
\n但是,如果您能找出更多信息,您将获得更有价值的答案。
\n例如,您的 adsl 可能更喜欢 1454 字节的缓冲区,那是因为 TCP/IP 的有效负载。对于磁盘,您可以使用与磁盘块大小匹配的值。

\n