我有一个从文件创建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这样的内容,以使帖子更小

有帮助吗?

解决方案

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

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

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

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

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

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

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

其他提示

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

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

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

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

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

我相信BufferedXStreams正在使用8192作为缓冲区大小,但像Ovidiu所说,你应该对一大堆选项进行测试。它真正依赖于文件系统和磁盘配置来确定最佳尺寸。

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

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

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

之后,我猜缓冲区大小并不重要。程序受I / O限制,并且BIS默认情况下缓冲区大小的增加不会对性能产生任何重大影响。

或者程序在MessageDigest.update()内部受CPU限制,并且大部分时间都没有花在应用程序代码中,因此调整它将无济于事。

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

1024适用于各种情况,但实际上,您可以通过更大或更小的缓冲区大小看到更好的性能。

这取决于许多因素,包括文件系统块 大小和CPU硬件。

对于缓冲区大小,选择2的幂是很常见的,因为大多数是基础 硬件采用fle块和高速缓存大小构成,功率为2.缓冲区 classes允许您在构造函数中指定缓冲区大小。如果没有提供,他们 使用默认值,在大多数JVM中为2的幂。

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

许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top