在使用FileInputStream时,如何确定理想的缓冲区大小?
我有一个从文件中创buildMessageDigest(哈希)的方法,我需要对很多文件(> = 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();
什么是最大化吞吐量的缓冲区的理想大小? 我知道这是依赖于系统,我很确定它的操作系统,文件系统和硬盘驱动器依赖,也可能有其他硬件/软件混合。
(我应该指出,我对Java有点新鲜,所以这可能只是一些我不知道的Java API调用。)
编辑:我不知道提前使用哪种系统,所以我不能假设很多。 (因为这个原因,我正在使用Java。)
编辑:上面的代码是失踪的东西try..catch使post变小
最佳缓冲区大小与许多因素有关:文件系统块大小,CPU高速caching大小和高速caching延迟。
大多数文件系统被configuration为使用块大小4096或8192.理论上,如果您configuration缓冲区大小,以便比磁盘块多读取几个字节,则对文件系统的操作可能效率极低(即如果您configuration您的缓冲区一次读取4100字节,每个读取将需要由文件系统读取2块)。 如果这些块已经在高速caching中,那么你最后付出的代价是RAM – > L3 / L2caching延迟。 如果你运气不好,而且这些块还没有在caching中,那么你还要支付磁盘 – > RAM的延迟时间。
这就是为什么你看到大多数缓冲区大小为2的幂,并且通常大于(或等于)磁盘块大小。 这意味着您的一个stream读取可能会导致多个磁盘块读取 – 但这些读取将始终使用一个完整的块 – 没有浪费的读取。
现在,在一个典型的stream式场景中,这会有相当大的偏移,因为从下一次读取(我们在这里进行顺序读取)时,从磁盘读取的数据块将仍然在内存中 – 所以,在下次读取时支付RAM – > L3 / L2caching等待时间价格,而不是磁盘 – > RAM等待时间。 在数量级方面,磁盘 – >内存延迟非常缓慢,几乎可以弥补您可能遇到的其他任何延迟。
所以,我怀疑如果你用不同的caching大小运行一个testing(我自己没有这样做),你可能会发现caching大小直到文件系统块的大小。 在此之上,我怀疑事情会很快平息。
这里有很多的条件和例外 – 系统的复杂性实际上是相当惊人的(仅仅是处理L3-> L2高速caching传输是令人难以置信的复杂的,并且随着每种CPUtypes而改变)。
这导致了“真实世界”的答案:如果你的应用程序有99%的存在,那么将caching大小设置为8192然后继续(甚至更好,select封装性能并使用BufferedInputStream来隐藏细节)。 如果您处于高度依赖于磁盘吞吐量的应用程序的1%,请制定您的实施scheme,以便换出不同的磁盘交互策略,并提供旋钮和拨号以允许用户testing和优化(或提出一些自我优化系统)。
是的,它可能依赖于各种各样的东西 – 但我怀疑它会造成很大的差异。 我倾向于select16K或32K作为内存使用和性能之间的良好平衡。
请注意,您应该在代码中使用try / finally块,以确保即使抛出exception也closuresstream。
在大多数情况下,这并不重要。 只要select一个好的大小,如4K或16K,坚持下去。 如果您确信这是应用程序的瓶颈,那么您应该开始分析以find最佳缓冲区大小。 如果select的尺寸太小,则会浪费时间执行额外的I / O操作和额外的函数调用。 如果你select一个太大的尺寸,你会看到很多caching未命中,这真的会减慢你的速度。 不要使用大于L2高速caching大小的缓冲区。
在理想的情况下,我们应该有足够的内存在一次读取操作中读取文件。 这将是performance最好的,因为我们让系统随意pipe理文件系统,分配单元和硬盘。 在实践中,您有幸提前知道文件大小,只需使用平均文件大小四舍五入到4K(NTFS上的默认分配单位)。 最重要的是:创build一个基准来testing多个选项。
使用Java NIO的FileChannel和MappedByteBuffer读取文件很可能会导致比任何涉及FileInputStream的解决scheme都快得多的解决scheme。 基本上,内存映射大文件,并使用小caching的直接缓冲区。
您可以使用BufferedStreams /阅读器,然后使用它们的缓冲区大小。
我相信BufferedXStreams使用8192作为缓冲区大小,但是像Ovidiu所说,你应该对一大堆选项进行testing。 它真的要取决于文件系统和磁盘configuration,以什么是最好的尺寸。
正如其他答案中已经提到的,使用BufferedInputStreams。
之后,我想缓冲区大小并不重要。 程序是I / O绑定的,并且在BIS默认情况下增长的缓冲区大小不会对性能产生任何大的影响。
或者该程序在MessageDigest.update()内部受CPU限制,大部分时间不在应用程序代码中使用,因此调整它将无济于事。
(嗯…多核心,线程可能会有所帮助。)
使缓冲区大到足以让大部分文件一次读取。 确保重复使用相同的缓冲区和相同的MessageDigest来读取不同的文件。
与这个问题无关:阅读Sun的代码约定,特别是在parens周围的空间和冗余大括号的使用。 避免运营商=
在一段while
或if
声明
1024适用于各种各样的情况,但在实践中,您可能会看到更好或更小缓冲区的性能。
这将取决于许多因素,包括文件系统块大小和CPU硬件。
由于大多数底层硬件都是以2的幂指定的fle块和高速caching大小构build的,所以select2的幂作为缓冲区大小也是常见的。缓冲类允许您在构造函数中指定缓冲区大小。 如果没有提供,则它们使用默认值,这在大多数JVM中是2的幂。
无论您select哪种缓冲区大小,您将看到的最大性能提升是从非缓冲文件迁移到缓冲文件访问。 调整缓冲区大小可能会略微提高性能,但除非您使用的是非常小的缓冲区或非常大的缓冲区大小,否则不会产生显着的影响。
在BufferedInputStream的源代码中你会发现:private static int DEFAULT_BUFFER_SIZE = 8192;
所以使用这个默认值是很重要的。
但是,如果你能找出更多的信息,你将会得到更有价值的答案。
例如,您的adsl可能会提供一个1454字节的缓冲区,这是因为TCP / IP的有效负载。 对于磁盘,可以使用与磁盘块大小相匹配的值。