使用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();

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

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

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

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


#1 热门回答(180 赞)

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

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

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

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

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

这里有atonof条件和例外 - 系统的复杂性实际上是非常惊人的(只是处理L3 - > L2缓存传输是令人难以置信的复杂,并且它随每种CPU类型而变化)。

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


#2 热门回答(14 赞)

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

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


#3 热门回答(7 赞)

在大多数情况下,这并不重要。只需选择一个好的尺寸,如4K或16K,并坚持下去。如果你认为这是你的应用程序的瓶颈,那么你应该开始分析以找到最佳的缓冲区大小。如果选择的尺寸太小,则会浪费时间进行额外的I / O操作和额外的函数调用。如果你选择一个太大的大小,你会开始看到很多缓存未命中,这会让你失望。不要使用大于L2缓存大小的缓冲区。