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

2022-08-31 07:03:42

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

编辑:上面的代码缺少诸如尝试之类的内容。抓住使帖子更小


答案 1

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

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

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

现在,这在典型的流式处理场景中偏移了相当多,因为当您点击下一次读取时,从磁盘读取的块仍将在内存中(毕竟我们在这里进行顺序读取) - 因此您最终在下次读取时支付RAM ->L3 / L2缓存延迟价格, 但不是磁盘>RAM延迟。就数量级而言,磁盘>RAM延迟非常慢,以至于它几乎淹没了您可能正在处理的任何其他延迟。

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

这里有大量的条件和例外 - 系统的复杂性实际上非常惊人(只需处理L3 - >L2缓存传输就非常复杂,并且随着每种CPU类型而变化)。

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


答案 2

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

请注意,您应该在代码中有一个 try/final 块,以确保即使引发异常,流也已关闭。


推荐