我有一个从文件创建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延迟是如此之慢,以至于几乎淹没了您可能要处理的任何其他延迟。
因此,我怀疑如果您使用不同的缓存大小运行测试(我自己没有这样做),则可能会发现缓存大小对文件系统块的大小有很大影响。除此之外,我怀疑情况会很快趋于平稳。
这里有很多条件和例外-系统的复杂性实际上是相当惊人的(仅仅掌握L3-> L2高速缓存传输的过程令人难以置信,并且每个CPU类型都会变化)。
这导致了"现实世界"的答案:如果您的应用程序有99%的可用空间,请将缓存大小设置为8192并继续运行(甚至更好,选择封装而不是性能,并使用BufferedInputStream隐藏细节)。如果您处于高度依赖磁盘吞吐量的1%的应用程序中,请精心设计实施方案,以便交换出不同的磁盘交互策略,并提供旋钮和转盘,以允许您的用户进行测试和优化(或提出一些建议)自我优化系统)。
我在Android应用程序的手机(Nexus 5X)上做了一些标记:小文件(3,5Mb)和大文件(175 Mb)。并发现黄金大小将为524288个长度的byte []。好吧,如果您根据文件大小在小缓冲区4Kb和大缓冲区524Kb之间进行切换,则可能会赢得10-20ms,但这并不值得。因此,在我的情况下,524 Kb是最好的选择。
是的,这可能取决于各种因素-但我怀疑这会带来很大的不同。我倾向于选择16K或32K作为内存使用和性能之间的良好平衡。
请注意,您应该在代码中包含try / finally块,以确保即使抛出异常也可以关闭流。
我编辑了有关try..catch的帖子。在我的真实代码中,我有一个,但是我省略了它,以使帖子更短。
如果我们要为其定义一个固定尺寸,哪个尺寸更好? 4k,16k或32k?
@MohammadrezaPanahi:请不要使用评论来badge用户。您等待了不到一个小时,才发表第二条评论。请记住,用户可以很容易地睡着,开会或基本上忙于其他事情,并且没有义务回答评论。但是要回答您的问题:这完全取决于上下文。如果您在内存非常有限的系统上运行,则可能需要一个较小的缓冲区。如果您在大型系统上运行,则使用较大的缓冲区将减少读取调用的次数。凯文·戴斯的回答非常好。
@JonSkeet很抱歉打扰您。因为我赶时间。
在大多数情况下,这并不重要。只需选择一个合适的尺寸(例如4K或16K)并坚持使用即可。如果您肯定这是应用程序中的瓶颈,那么应该开始进行性能分析以找到最佳的缓冲区大小。如果选择的尺寸过小,则会浪费时间进行额外的I / O操作和额外的函数调用。如果选择的尺寸太大,则会开始看到很多缓存未命中,这实际上会使您的速度降低。不要使用大于二级缓存大小的缓冲区。
使用Java NIO的FileChannel和MappedByteBuffer读取文件很可能会导致解决方案比任何涉及FileInputStream的解决方案都快得多。基本上,内存映射大文件,并为小文件使用直接缓冲区。
您可以使用BufferedStreams / reader,然后使用它们的缓冲区大小。
我相信BufferedXStreams使用8192作为缓冲区大小,但是就像Ovidiu所说的那样,您可能应该对很多选项进行测试。最佳大小的确取决于文件系统和磁盘配置。
在理想情况下,我们应该有足够的内存以一次读取操作读取文件。
那将是最佳性能,因为我们让系统随意管理文件系统,分配单元和HDD。
在实践中,您很幸运地提前知道了文件大小,只需使用四舍五入至4K(NTFS上的默认分配单位)的平均文件大小即可。
最重要的是:创建一个基准来测试多个选项。
您是说文件读写的最佳缓冲区大小是4k?
在BufferedInputStream的源代码中,您将找到:private static int DEFAULT_BUFFER_SIZE = 8192;
因此,使用该默认值是可以的。
但是,如果您能找到更多的信息,您将获得更有价值的答案。
例如,您的adsl可能会提供1454字节的缓冲区,这是因为TCP / IP的有效负载。对于磁盘,您可以使用与磁盘的块大小匹配的值。
正如其他答案中已经提到的那样,请使用BufferedInputStreams。
在那之后,我猜缓冲区的大小并不重要。这两个程序都是受I / O约束的,并且缓冲区大小超过BIS默认值将不会对性能产生太大影响。
或者该程序在MessageDigest.update()中绑定了CPU,并且大部分时间没有花费在应用程序代码中,因此进行调整将无济于事。
(嗯...有多个核心,线程可能会有所帮助。)
1024在各种情况下都适用,尽管实际上在使用更大或更小的缓冲区时,您可能会看到更好的性能。
这将取决于许多因素,包括文件系统块
大小和CPU硬件。
通常为缓冲区大小选择2的幂,因为大多数底层
硬件由fle块和缓存大小(为2的幂)构成。
类允许您在构造函数中指定缓冲区大小。如果没有提供,则它们
使用默认值,在大多数JVM中,默认值为2的幂。
无论您选择哪种缓冲区大小,都将获得最大的性能提升
看到正在从非缓冲文件访问转移到缓冲文件访问。调整缓冲区大小可能
稍微提高性能,但除非您使用的是极小或极高的
如果缓冲区大小较大,则影响不大。