【问题标题】:Why does Java disk I/O perform so much slower than the equivalent I/O code written in C?为什么 Java 磁盘 I/O 的执行速度比用 C 编写的等效 I/O 代码慢得多?
【发布时间】:2015-06-27 23:00:45
【问题描述】:

我有一个 SSD 磁盘,每个规格应提供不少于 10k IOPS。我的基准测试证实它可以为我提供 20k IOPS。

然后我创建这样一个测试:

private static final int sector = 4*1024;
private static byte[] buf = new byte[sector];
private static int duration = 10; // seconds to run
private static long[] timings = new long[50000];
public static final void main(String[] args) throws IOException {
    String filename = args[0];
    long size = Long.parseLong(args[1]);
    RandomAccessFile raf = new RandomAccessFile(filename, "r");
    Random rnd = new Random();
    long start = System.currentTimeMillis();
    int ios = 0;
    while (System.currentTimeMillis()-start<duration*1000) {
        long t1 = System.currentTimeMillis();
        long pos = (long)(rnd.nextDouble()*(size>>12));
        raf.seek(pos<<12);
        int count = raf.read(buf);
        timings[ios] = System.currentTimeMillis() - t1;
        ++ios;
    }
    System.out.println("Measured IOPS: " + ios/duration);
    int totalBytes = ios*sector;
    double totalSeconds = (System.currentTimeMillis()-start)/1000.0;
    double speed = totalBytes/totalSeconds/1024/1024;
    System.out.println(totalBytes+" bytes transferred in "+totalSeconds+" secs ("+speed+" MiB/sec)");
    raf.close();
    Arrays.sort(timings);
    int l = timings.length;
    System.out.println("The longest IO = " + timings[l-1]);
    System.out.println("Median duration = " + timings[l-(ios/2)]);
    System.out.println("75% duration = " + timings[l-(ios * 3 / 4)]);
    System.out.println("90% duration = " + timings[l-(ios * 9 / 10)]);
    System.out.println("95% duration = " + timings[l-(ios * 19 / 20)]);
    System.out.println("99% duration = " + timings[l-(ios * 99 / 100)]);
}

然后我运行这个示例并得到 2186 IOPS:

$ sudo java -cp ./classes NioTest /dev/disk0 240057409536
Measured IOPS: 2186
89550848 bytes transferred in 10.0 secs (8.540234375 MiB/sec)
The longest IO = 35
Median duration = 0
75% duration = 0
90% duration = 0
95% duration = 0
99% duration = 0

为什么它的工作速度比 C 中的相同测试慢得多?

更新:这是提供 20k IOPS 的 Python 代码:

def iops(dev, blocksize=4096, t=10):

    fh = open(dev, 'r')
    count = 0
    start = time.time()
    while time.time() < start+t:
        count += 1
        pos = random.randint(0, mediasize(dev) - blocksize) # need at least one block left
        pos &= ~(blocksize-1)   # sector alignment at blocksize
        fh.seek(pos)
        blockdata = fh.read(blocksize)
    end = time.time()
    t = end - start
    fh.close()

Update2:NIO代码(只是一段,不会复制所有方法)

...
RandomAccessFile raf = new RandomAccessFile(filename, "r");
InputStream in = Channels.newInputStream(raf.getChannel());
...
int count = in.read(buf);
...

【问题讨论】:

  • 你在 Java 和 C 中使用相同的随机数序列吗?请注意,原始磁盘传输速度无关紧要。对于随机访问,您需要查看查找时间。
  • 为什么将 40000 个 .java 文件写入我的袖珍 USB 驱动器需要 8 分钟。与我撕掉的 1 mp4 的 20 秒(相同的累积大小)相比?我要退款(用于 USB 驱动器)
  • 用 C 语言发布相同测试的代码,以便读者可以确定正在比较的内容。
  • 我怀疑你不是在比较同类。除非您明确使用缓冲,否则不会缓冲 Java IO。除非您使用低级 API,否则默认情况下标准 C API 会缓冲。你的 C 代码是什么样的?
  • 有趣的是该类名为 NioTest 但不包含 NIO 代码。虽然有大量证据表明使用 NIO 并不能保证速度提高,但我仍然希望看到使用 FileChannel 完成相同的测试,甚至可能使用 MappedByteBuffer,因为该问题声称 Java 本身存在缺陷。

标签: java performance io java-8 solid-state-drive


【解决方案1】:

您的问题是基于一个错误的假设,即类似于您的 Java 代码的 C 代码会像 IOMeter 一样执行。因为这个假设是错误的,所以 C 性能和 Java 性能之间没有差异可以解释。

如果您的问题是为什么您的 Java 代码相对于 IOMeter 执行得如此糟糕,那么答案是 IOMeter 不像您的代码那样一次发出一个请求。要从 SSD 获得全部性能,您需要保持其请求队列非空,并且等待每次读取完成后再发出下一次读取是不可能的。

尝试使用线程池来发出您的请求。

【讨论】:

    【解决方案2】:

    从这篇过时的文章来看,传统的 java 随机访问速度要慢 2.5 到 3.5 倍。这是一份研究 pdf,所以不要怪我点击它。

    链接:http://pages.cs.wisc.edu/~guo/projects/736.pdf

    Java 原始 I/O 比 C/C++ 慢,因为 Java 中的系统调用是 更贵;缓冲提高了 Java I/O 性能,因为它 减少系统调用,但更大的缓冲区大小并没有太大的收益; 直接缓冲优于 Java 提供的缓冲 I/O 类,因为用户可以根据自己的需要对其进行定制;增加 操作大小有助于 I/O 性能而没有开销;和系统 调用在 Java 本地方法中很便宜,而调用的开销 本机方法相当高。当本地调用次数为 适当减少,可以达到与 C/C++ 相当的性能。

    从那个时代开始就是你的代码。现在让我们重写它,不要使用RandomAccessFile,而是使用java.nio,好吗?

    我有一些 nio2 代码可以用来对抗 C。垃圾收集可以排除 :)

    【讨论】:

    • 我认为我做错了什么。我只是不知道出了什么问题。我尝试了 NIO,但得到了相同的 IOPS。如果您能提出替代代码,将不胜感激。
    • 不会更快,但不会慢 3.5 倍
    • 我以前只做汇编和 C,所以我没有妄想。好吧,大多数情况下不会。
    【解决方案3】:

    因为您使用的是RandomAccessFile,这是 Java 中最慢的磁盘 I/O 方法之一。

    尝试使用更快的东西,比如BufferedInputStreamBufferedOutputStream,看看你能达到什么速度。

    如果您想知道为什么这会对 SSD 产生影响(因为 SSD 应该擅长随机访问),这与访问的随机性无关;这是关于带宽的。如果您的 SSD 具有 1024 位宽的总线,但每次写入仅写入 64 位(就像您通过写入 longs 或 doubles 所做的那样),您的速度会变慢。 (当然,这些数字只是为了举例。)

    现在,我可以看到这不是您的代码正在做的事情(或者至少看起来正在做的事情),但 RandomAccessFile 很有可能在后台以这种方式实现它。再次尝试使用缓冲流,看看会发生什么。

    【讨论】:

    • BufferedInputStream 从什么时候开始需要 2 TB 内存?
    • 你是否知道BufferedInputStream是用于顺序读取(而我的测试是用于随机读取)?
    • 这个答案根本与我的问题无关。
    • @Antonio 你问了一个关于 SO 的问题——任何人都可以免费帮助你。如果您不同意某个答案/认为它是错误的,请投反对票并继续前进。不要要求某人删除答案。另外,请不要粗鲁/好战(在 cmets 或有问题的标题中)。
    • @Antonio - 得到一个答案并不妨碍你得到另一个答案。对回答你的人很粗鲁。请理解 RedRoboHood 自愿抽出时间来帮助您。
    【解决方案4】:

    RandomAccess 在 Java 中的速度非常快,但无法与 C 相比。 但是,如果您想更好地比较 JVM 上的 IO 性能,请阅读 Martin Thompson 关于该主题的优秀博客:http://mechanical-sympathy.blogspot.co.uk/2011/12/java-sequential-io-performance.html

    【讨论】:

    • 不熟练的文章。误导的非常肮脏的结果。我在提问之前阅读了它。
    猜你喜欢
    • 1970-01-01
    • 2014-05-20
    • 2015-04-17
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-12-07
    • 2015-05-22
    相关资源
    最近更新 更多