使用 Java 进行硬盘基准测试,得到不合理的快速结果

Posted

技术标签:

【中文标题】使用 Java 进行硬盘基准测试,得到不合理的快速结果【英文标题】:Hard drive benchmarking with Java, getting unreasonably fast results 【发布时间】:2016-10-13 16:28:54 【问题描述】:

我编写了一个代码来对硬盘进行基准测试。这相当简单:使用 BufferedOutputStream 将一大块字节(2500 * 10M)写入磁盘,然后使用 BufferedInputStream 读取。我每次写 2500 字节 10M 次来模拟我写的另一个程序中的条件。还有另一个全局变量,“无意义”,它是用读取的字节计算的——它绝对没有任何意义,只是用来强制执行实际读取和使用字节(避免没有读取字节的情况由于一些优化)。

代码运行4次,输出结果。

这里是:

import java.io.*;

public class DriveTest

    public static long meaningless = 0;

    public static String path = "C:\\test";

    public static int chunkSize = 2500;

    public static int iterations = 10000000;

    public static void main(String[] args)
    
        try
        
            for(int i = 0; i < 4; i++)
            
                System.out.println("Test " + (i + 1) + ":");
                System.out.println("==================================");

                write();
                read();

                new File(path).delete();

                System.out.println("==================================");
            
        
        catch(Exception e)
        
            e.printStackTrace();
        
    

    private static void write() throws Exception
    
        BufferedOutputStream bos = new BufferedOutputStream(
                                   new FileOutputStream(new File(path)));

        long t1 = System.nanoTime();

        for(int i = 0; i < iterations; i++)
        
            byte[] data = new byte[chunkSize];

            for(int j = 0; j < data.length; j++)
            
                data[j] = (byte)(j % 127);
            

            bos.write(data);
        

        bos.close();

        long t2 = System.nanoTime();

        double seconds = ((double)(t2 - t1) / 1000000000.0);

        System.out.println("Writing took " + (t2 - t1) + 
                           " ns (" + seconds + " seconds).");

        System.out.println("Write rate " + (((double)chunkSize * 
                           iterations / seconds) / 
                           (1024.0 * 1024.0)) + " MB/s.");
    

    private static void read() throws Exception
    
        BufferedInputStream bis = new BufferedInputStream(
                                  new FileInputStream(new File(path)));

        long t1 = System.nanoTime();

        byte[] data;

        for(int i = 0; i < iterations; i++)
        
            data = new byte[chunkSize];

            bis.read(data);

            meaningless += data[i % chunkSize];
        

        bis.close();

        long t2 = System.nanoTime();

        System.out.println("meaningless is: " + meaningless + ".");

        double seconds = ((double)(t2 - t1) / 1000000000.0);

        System.out.println("Reading Took " + (t2 - t1) + 
                           " ns, which is " + 
                           seconds + " seconds.");

        System.out.println("Read rate " + (((double)chunkSize * 
                           iterations / seconds) / 
                           (1024.0 * 1024.0)) + " MB/s.");
    

这里的问题是双重的:

    当迭代次数 = 10M(将 ~23GB 写入磁盘)时,常规 7200 RPM 驱动器可提供非常快的结果,高于规格:

_

Test 1:
Writing took 148738975163 ns (148.738975163 seconds).
Write rate 160.29327810029918 MB/s.
meaningless is: 1246080000.
Reading Took 139143051529 ns, which is 139.143051529 seconds.
Read rate 171.34781541848795 MB/s.

Test 2:
Writing took 146591885655 ns (146.591885655 seconds).
Write rate 162.64104799270686 MB/s.
meaningless is: 1869120000.
Reading Took 139845492688 ns, which is 139.845492688 seconds.
Read rate 170.48713871206587 MB/s.

Test 3:
Writing took 152049678671 ns (152.049678671 seconds).
Write rate 156.8030798785472 MB/s.
meaningless is: 2492160000.
Reading Took 140152776858 ns, which is 140.152776858 seconds.
Read rate 170.11334662539255 MB/s.

Test 4:
Writing took 151363950081 ns (151.363950081 seconds).
Write rate 157.51344951950355 MB/s.
meaningless is: 3115200000.
Reading Took 139176911081 ns, which is 139.176911081 seconds.
Read rate 171.30612919179143 MB/s.

这看起来很奇怪——磁盘真的能达到这样的速度吗?我严重怀疑,考虑到经过测试的规范(甚至在 java 输出/输入流下——在我的新手看来——不应该是最佳的!)更低:http://hdd.userbenchmark.com/Toshiba-DT01ACA200-2TB/Rating/2736

    当迭代次数设置为 1M (1000000) 时,数字会变得非常疯狂:

_

Test 1:
Writing took 6918084976 ns (6.918084976 seconds).
Write rate 344.6308912490619 MB/s.
meaningless is: 62304000.
Reading Took 2060226375 ns, which is 2.060226375 seconds.
Read rate 1157.244572706543 MB/s.

Test 2:
Writing took 6970893036 ns (6.970893036 seconds).
Write rate 342.0201369756931 MB/s.
meaningless is: 124608000.
Reading Took 2013661185 ns, which is 2.013661185 seconds.
Read rate 1184.0054368508995 MB/s.

Test 3:
Writing took 7140592101 ns (7.140592101 seconds).
Write rate 333.89188981705496 MB/s.
meaningless is: 186912000.
Reading Took 2011346987 ns, which is 2.011346987 seconds.
Read rate 1185.367719456367 MB/s.

Test 4:
Writing took 7140064035 ns (7.140064035 seconds).
Write rate 333.91658384694375 MB/s.
meaningless is: 249216000.
Reading Took 2041787713 ns, which is 2.041787713 seconds.
Read rate 1167.6952387535623 MB/s.

这是什么缓存魔法?? (什么样的缓存可以使 writing 更快??) :) 它怎么能被撤消呢?我已经读写了2.3GB的文件!如果确实是问题所在,这需要做一些巨大的缓存。

谢谢

【问题讨论】:

您的顺序写入速率看起来很合理。我有一个 5900rpm 的外置硬盘,通常具有大约 120MB/s 的性能。 你检查过系统的FS缓存吗?我假设您的系统的内存介于 >2.3 和 16GB :) 但是缓存并不能解释写入结果,对吧? 你会定义的。需要在进行写入测量之前关闭流,因为除非您关闭(或刷新)它,否则它可能不会将其刷新到磁盘。不过,这并不能解释读取性能...... 谢谢,我只是改了一下(把stream的关闭移到计算第二个时间点t2之前),结果还是一样。对于普通 HDD 和 2.3GB 的文件,我以 ~325 MB/s 的速度写入,以 ~1150 MB/s 的速度读取。这太奇怪了。 【参考方案1】:

您的测试可能只是读取和写入操作系统页面缓存。小数据大小将完全适合。较大的不会被操作系统异步刷新。您应该尝试使用 OpenOptions.DSYNC 和 SYNC 选项。

【讨论】:

非常感谢。这似乎是一个缓存问题(异步写入可能解释了加速)。我正在监视 RAM 的使用情况,实际上它在程序的每次迭代中达到了大约 1.5GB 的“修改”峰值,然后又下降了。如何将 StandardOpenOptions 枚举与缓冲流一起使用?我应该把它放在哪里,或者我在哪里静态设置它? @amirkr:write() 系统调用可以并且确实在数据进入页面缓存后立即返回。他们不会等到数据到达磁盘,因为有充分的理由甚至不立即启动对磁盘的写入。 (例如,它可能是一个即将被删除的 tmp 文件,在这种情况下,如果它的数据根本没有进入磁盘就可以了。) @PeterCordes 但在这种情况下写入速度不会很快吗?如果数据只是在高速缓存中,即在超高速主存储器中,则写入将在几分之一秒内结束。这里有很多变量(缓冲、缓存、操作系统限制、硬盘缓存、硬盘吞吐量、java 对象缓冲区......几乎不可能估计速度:)) 我仍然不清楚 DSYNC 和 SYNC 参数(java.nio 的一部分)如何与缓冲流 (java.io) 一起使用。有什么想法吗? @amirkr 他们不能。您必须使用 FileChannels。即使那样,那也只会解决您的写作问题。我认为除非你先刷新它,否则读取仍然会命中页面缓存。

以上是关于使用 Java 进行硬盘基准测试,得到不合理的快速结果的主要内容,如果未能解决你的问题,请参考以下文章

java 使用一些基准测试代码实现Naive递归快速排序。请注意,此实现仅用于演示。它会失败

浅谈基准测试

几款优秀的Linux基准测试工具

用于对 Java 类进行基准测试的 JMH 与 JMeter?

Java优化实战「微基准系列」带你脚踏实地的进行开发和使用JMH测试和提升应用程序和服务指南

使用benchmark.js进行前端代码基准测试