【问题标题】:How do you determine the ideal buffer size when using FileInputStream?使用 FileInputStream 时如何确定理想的缓冲区大小?
【发布时间】:2010-09-19 04:59:59
【问题描述】:

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

【问题讨论】:

    标签: java performance file-io filesystems buffer


    【解决方案1】:

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

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

    【讨论】:

    • 我编辑了关于 try..catch 的帖子。在我的真实代码中,我有一个,但为了使帖子更短,我省略了它。
    • 如果我们想为它定义一个固定的尺寸,哪个尺寸更好? 4k、16k 还是 32k?
    • @MohammadrezaPanahi:请不要使用 cmets 来骚扰用户。您在第二条评论前等待了 不到一个小时。请记住,用户很容易入睡,或者在开会,或者基本上忙于其他事情,并且没有义务回答 cmets。但要回答你的问题:这完全取决于上下文。如果您在内存非常有限的系统上运行,您可能需要一个小的缓冲区。如果您在大型系统上运行,使用更大的缓冲区将减少读取调用的次数。 Kevin Day 的回答非常好。
    【解决方案2】:

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

    【讨论】:

    • 你的意思是文件中读写的最佳缓冲区大小是4k吗?
    【解决方案3】:

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

    我相信 BufferedXStreams 使用 8192 作为缓冲区大小,但正如 Ovidiu 所说,您可能应该对一大堆选项进行测试。它实际上将取决于文件系统和磁盘配置来确定最佳大小。

    【讨论】:

      【解决方案4】:

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

      【讨论】:

        【解决方案5】:

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

        在那之后,我猜缓冲区大小并不重要。要么程序受 I/O 限制,而且缓冲区大小超过 BIS 默认值,不会对性能产生任何重大影响。

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

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

        【讨论】:

          【解决方案6】:

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

          【讨论】:

            【解决方案7】:

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

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

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

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

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

            这里有的条件和例外——系统的复杂性实际上是相当惊人的(仅仅处理 L3 -> L2 缓存传输是令人难以置信的复杂,它会随着每种 CPU 类型)。

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

            【讨论】:

            • 我在手机 (Nexus 5X) 上为我的 Android 应用程序做了一些基准测试:小文件 (3,5Mb) 和大文件 (175 Mb)。并发现黄金大小将是 524288 长度的 byte[]。好吧,如果您根据文件大小在小缓冲区 4Kb 和大缓冲区 524Kb 之间切换,您可能会赢得 10-20 毫秒,但这不值得。所以 524 Kb 是我的最佳选择。
            【解决方案8】:

            1024 适用于多种情况,但在实践中,您可能会看到更大或更小的缓冲区大小会带来更好的性能。

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

            为缓冲区大小选择 2 的幂也很常见,因为大多数底层 硬件由 2 的幂的文件块和缓存大小构成。缓冲的 类允许您在构造函数中指定缓冲区大小。如果没有提供,他们 使用默认值,在大多数 JVM 中是 2 的幂。

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

            【讨论】:

              【解决方案9】:

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

              【讨论】:

                猜你喜欢
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 2023-03-24
                • 1970-01-01
                • 1970-01-01
                • 2020-09-05
                • 2012-05-28
                相关资源
                最近更新 更多