一尘不染

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

java

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

最大化吞吐量的理想缓冲区大小是多少?我知道这是与系统有关的,并且我很确定它与操作系统,文件系统和 HDD有关,并且可能还有其他硬件/软件。

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

编辑:我不提前知道将要使用的系统种类,所以我不能承担很多。(出于这个原因,我使用Java。)

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


阅读 723

收藏
2020-03-06

共1个答案

一尘不染

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

大多数文件系统配置为使用4096或8192的块大小。理论上,如果配置缓冲区大小,以便读取的内容比磁盘块多几个字节,则文件系统的操作效率极低(即配置您的缓冲区一次读取4100字节,每次读取将需要文件系统进行2次块读取)。如果块已经在缓存中,那么您就要付出RAM-> L3 / L2缓存延迟的代价。如果您不走运,并且块尚未在缓存中,那么您还要付出磁盘-> RAM延迟的代价。

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

现在,在典型的流传输方案中,这可以抵消很多,因为从磁盘读取的块将在您下一次读取时仍在内存中(毕竟,我们在这里进行顺序读取)-这样就结束了在下一次读取时为RAM-> L3 / L2缓存延迟时间付出代价,而不是磁盘-> RAM延迟。就数量级而言,磁盘-> RAM延迟是如此之慢,以至于几乎淹没了您可能要处理的任何其他延迟。

因此,我怀疑如果您使用不同的缓存大小运行测试(我自己没有做过),您可能会发现缓存大小(取决于文件系统块的大小)的影响很大。除此之外,我怀疑情况会很快趋于平稳。

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

这导致了“现实世界”的答案:如果您的应用程序有99%的可用空间,请将缓存大小设置为8192并继续运行(甚至更好,选择封装而不是性能,并使用BufferedInputStream隐藏细节)。如果您处于高度依赖磁盘吞吐量的1%的应用程序中,请精心设计实施方案,以便交换出不同的磁盘交互策略,并提供旋钮和转盘以允许用户进行测试和优化(或提出一些建议)。自我优化系统)。

2020-03-06