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


156

我有一个从文件创建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之类的东西,以使帖子更小

Answers:


213

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

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

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

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

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

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

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


3
我在Android应用程序的手机(Nexus 5X)上做了一些标记:小文件(3,5Mb)和大文件(175 Mb)。并发现黄金大小将为524288个长度的byte []。好吧,如果您根据文件大小在4Kb小缓冲区和524Kb大缓冲区之间进行切换,则可能会赢得10-20ms,但这并不值得。因此,在我的情况下,524 Kb是最好的选择。
Kirill Karmazin

19

是的,这可能取决于各种因素-但我怀疑这会带来很大的不同。我倾向于选择16K或32K,以在内存使用和性能之间取得良好的平衡。

请注意,您应该在代码中包含try / finally块,以确保即使抛出异常也可以关闭流。


我编辑了有关try..catch的帖子。在我的真实代码中,我有一个,但是为了使帖子更短,我省略了它。
ARKBAN

1
如果我们要为其定义一个固定尺寸,哪个尺寸更好?4k,16k或32k?
BattleTested

2
@MohammadrezaPanahi:请不要使用评论来badge用户。您等待了不到一个小时才发表第二条评论。请记住,用户可以很容易地睡着,开会或基本上忙于其他事情,并且没有义务回答评论。但是要回答您的问题:这完全取决于上下文。如果您在内存受限的系统上运行,则可能需要一个较小的缓冲区。如果您在大型系统上运行,则使用较大的缓冲区将减少读取调用的次数。凯文·戴的答案非常好。
乔恩·斯基特

7

在大多数情况下,这并不重要。只需选择4K或16K之类的合适尺寸并坚持下去即可。如果您肯定这是应用程序中的瓶颈,那么应该开始进行性能分析以找到最佳的缓冲区大小。如果选择的尺寸过小,则会浪费时间进行额外的I / O操作和额外的函数调用。如果选择的尺寸太大,则会开始看到很多缓存未命中,这确实会使您的速度变慢。不要使用大于二级缓存大小的缓冲区。


4

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


您是说文件读写的最佳缓冲区大小是4k?
BattleTested

4

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

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


4

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


4

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


1

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

在那之后,我猜缓冲区的大小并不重要。这两个程序都是受I / O约束的,并且缓冲区大小超过BIS默认值将不会对性能产生太大影响。

或者该程序在MessageDigest.update()中绑定了CPU,并且大部分时间都没有花在应用程序代码中,因此进行调整将无济于事。

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


0

1024在各种情况下都适用,尽管在实践中您可能会看到较大或较小的缓冲区大小会获得更好的性能。

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

通常为缓冲区大小选择2的幂,因为大多数底层硬件的结构是fle块和缓存大小为2的幂。Buffered类允许您在构造函数中指定缓冲区的大小。如果未提供任何值,则它们使用默认值,在大多数JVM中,该默认值为2的幂。

无论选择哪种缓冲区大小,您将看到的最大性能提升是从非缓冲文件访问变为缓冲文件访问。调整缓冲区大小可能会稍微改善性能,但是除非您使用的缓冲区大小过小或过大,否则不太可能产生重大影响。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.