【问题标题】:Testing disk performance: differs with and without using Java测试磁盘性能:使用和不使用 Java 的不同
【发布时间】:2015-11-15 15:27:36
【问题描述】:

我被要求测量当前磁盘性能,因为我们计划在我们的应用服务器上用网络附加存储替换本地磁盘。由于我们写入数据的应用程序是用 Java 编写的,我想我会直接在 Linux 中测量性能,并使用简单的 Java 测试。然而,我得到了截然不同的结果,特别是在读取数据时,使用在我看来类似的测试。直接在Linux中我正在做:

dd if=/dev/zero of=/data/cache/test bs=1048576 count=8192
dd if=/data/cache/test of=/dev/null bs=1048576 count=8192

我的 Java 测试如下所示:

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;

public class TestDiskSpeed {
private byte[] oneMB = new byte[1024 * 1024];

public static void main(String[] args) throws IOException {
    new TestDiskSpeed().execute(args);
}

private void execute(String[] args) throws IOException {
    long size = Long.parseLong(args[1]);
    testWriteSpeed(args[0], size);
    testReadSpeed(args[0], size);
}


private void testWriteSpeed(String filePath, long size) throws IOException {
    File file = new File(filePath);
    BufferedOutputStream writer = null;
    long start = System.currentTimeMillis();
    try {
        writer = new BufferedOutputStream(new FileOutputStream(file), 1024 * 1024);
        for (int i = 0; i < size; i++) {
            writer.write(oneMB);
        }
        writer.flush();
    } finally {
        if (writer != null) {
            writer.close();
        }
    }
    long elapsed = System.currentTimeMillis() - start;
    String message = "Wrote " + size + "MB in " + elapsed + "ms at a speed of " + calculateSpeed(size, elapsed) + "MB/s";
    System.out.println(message);
}

private void testReadSpeed(String filePath, long size) throws IOException {
    File file = new File(filePath);
    BufferedInputStream reader = null;
    long start = System.currentTimeMillis();
    try {
        reader = new BufferedInputStream(new FileInputStream(file), 1024 * 1024);
        for (int i = 0; i < size; i++) {
            reader.read(oneMB);
        }
    } finally {
        if (reader != null) {
            reader.close();
        }
    }
    long elapsed = System.currentTimeMillis() - start;
    String message = "Read " + size + "MB in " + elapsed + "ms at a speed of " + calculateSpeed(size, elapsed) + "MB/s";
    System.out.println(message);
}

private double calculateSpeed(long size, long elapsed) {
    double seconds = ((double) elapsed) / 1000L;
    double speed = ((double) size) / seconds;
    return speed;
}

}

这是通过“java TestDiskSpeed /data/cache/test 8192”调用的

这两个都应该创建 8GB 的​​零文件,一次 1MB,测量速度,然后读回并再次测量。然而,我一直获得的速度是:

Linux:写入 - ~650MB/s

Linux:读取 - ~4.2GB/s

Java:写入 - ~500MB/s

Java:读取 - ~1.9GB/s

谁能解释这个巨大的差异?

【问题讨论】:

  • 基本上你是在测试磁盘缓存的读取速度。那是你的目标吗?在这种情况下,Java 的开销可能很大。
  • 这是 Martin Thompson 关于 Java I/O 性能的good post
  • 缓冲仅在您进行大量小读取时才有帮助。无论如何,我都会使用 NIO 进行这个读/写测试。
  • @peter 我对由 apangin 链接的 Martin 的代码进行了相当多的测试,我可以确认他的观察,即 NIO 代码需要很长时间才能预热,然后只能与普通阻塞 InputStream 相提并论。
  • @apangin 在 cmets 部分的深处有一个重要的观察结果:FileInputStream 的性能与 RandomAccessFile 一样好。

标签: java performance disk dd


【解决方案1】:

当我在我的系统上使用 NIO 运行它时。带有 i7-3970X 的 Ubuntu 15.04

public class Main {
    static final int SIZE_GB = Integer.getInteger("sizeGB", 8);
    static final int BLOCK_SIZE = 64 * 1024;

    public static void main(String[] args) throws IOException {
        ByteBuffer buffer = ByteBuffer.allocateDirect(BLOCK_SIZE);
        File tmp = File.createTempFile("delete", "me");
        tmp.deleteOnExit();
        int blocks = (int) (((long) SIZE_GB << 30) / BLOCK_SIZE);
        long start = System.nanoTime();
        try (FileChannel fc = new FileOutputStream(tmp).getChannel()) {
            for (int i = 0; i < blocks; i++) {
                buffer.clear();
                while (buffer.remaining() > 0)
                    fc.write(buffer);
            }
        }
        long mid = System.nanoTime();
        try (FileChannel fc = new FileInputStream(tmp).getChannel()) {
            for (int i = 0; i < blocks; i++) {
                buffer.clear();
                while (buffer.remaining() > 0)
                    fc.read(buffer);
            }
        }
        long end = System.nanoTime();

        long size = tmp.length();
        System.out.printf("Write speed %.1f GB/s, read Speed %.1f GB/s%n",
                (double) size/(mid-start), (double) size/(end-mid));

    }
}

打印

Write speed 3.8 GB/s, read Speed 6.8 GB/s

【讨论】:

  • 您的代码与 Martin 的代码之间的主要区别在于,Martin 还结合了典型的习惯用法来使用数据:buffer.flip(); while (buffer.hasRemaining()) { checkSum += buffer.get(); }InputStream 的类似代码。因此,也许纯 I/O 传输到缓冲区很快,但它被消费代码淹没了。最重要的一点是,如果从磁盘缓存中检索数据,I/O 将不再是瓶颈。
  • @MarkoTopolnik 虽然是这样,但问题中的比较是dd 写入大量零或复制到 /dev/null。你是对的,一个重要的程序会花费大部分时间做一些有用的事情。
  • 当然,我不认为这是问题的正确答案。事实上,我应该在问题下的 cmets 中回答。我运行了你的代码,得到了更多:7.5 GB/s。
  • @MarkoTopolnik OP 很幸运能够在数据 esp 文本的实际处理中获得 500 MB/s。顺便说一句,此时 SATA 驱动器已达到最大值。
  • 是的,这是正确的估计。在我自己的努力中,我正在努力以超过 1 GB/s 的速率读取压缩数据,即使使用 5:1 压缩和最快的本地代码 (LZ4),我也几乎无法实现。解压算法几乎不做任何处理,它只是根据压缩文件中编码的指令复制数据块。
【解决方案2】:

如果您删除 BufferedXxxStream,您可能会获得更好的性能。这无济于事,因为您正在进行 1Mb 读/写,并且会导致数据的额外内存副本。

更好的是,您应该使用 NIO 类而不是常规 IO 类。

最终尝试

你应该清理你的 try-finally 代码。

// Original code
BufferedOutputStream writer = null;
try {
    writer = new ...;
    // use writer
} finally {
    if (writer != null) {
        writer.close();
    }
}

// Cleaner code
BufferedOutputStream writer = new ...;
try {
    // use writer
} finally {
    writer.close();
}

// Even cleaner, using try-with-resources (since Java 7)
try (BufferedOutputStream writer = new ...) {
    // use writer
}

【讨论】:

    【解决方案3】:

    为了补充 Peter 的出色回答,我将添加以下代码。它将老牌java.io 与 NIO 的性能进行了正面对比。与 Peter 不同的是,我不只是将数据读入直接缓冲区,而是用它做了一件典型的事情:将其传输到堆上字节数组中。这对性能的影响非常小:我使用 Peter 的代码获得 7.5 GB/s,而在这里我获得了 6.0 GB/s。

    对于java.io 方法,我不能使用直接缓冲区,而是直接使用我的目标堆上字节数组调用read 方法。请注意,此数组很小,并且大小为 555 字节,非常尴尬。尽管如此,我还是获得了几乎相同的性能:5.6 GB/s。差异是如此之小,以至于在正常使用中它会完全消失,即使在我不直接从磁盘缓存中读取的情况下也是如此。

    作为奖励,我在底部提供了一种可用于 Linux 和 Mac 来清除磁盘缓存的方法。如果您决定在写入和读取步骤之间调用它,您会看到性能的巨大转变。

    public final class MeasureIOPerformance {
            static final int SIZE_GB = Integer.getInteger("sizeGB", 8);
            static final int BLOCK_SIZE = 64 * 1024;
            static final int blocks = (int) (((long) SIZE_GB << 30) / BLOCK_SIZE);
            static final byte[] acceptBuffer = new byte[555];
    
            public static void main(String[] args) throws IOException {
                for (int i = 0; i < 3; i++) {
                    measure(new ChannelRw());
                    measure(new StreamRw());
                }
            }
    
            private static void measure(RW rw) throws IOException {
                File file = File.createTempFile("delete", "me");
                file.deleteOnExit();
                System.out.println("Writing " + SIZE_GB + " GB " + " with " + rw);
                long start = System.nanoTime();
                rw.write(file);
                long mid = System.nanoTime();
                System.out.println("Reading " + SIZE_GB + " GB " + " with " + rw);
                long checksum = rw.read(file);
                long end = System.nanoTime();
                long size = file.length();
                System.out.printf("Write speed %.1f GB/s, read Speed %.1f GB/s%n",
                        (double) size/(mid-start), (double) size/(end-mid));
                System.out.println(checksum);
                file.delete();
            }
    
            interface RW {
                void write(File f) throws IOException;
                long read(File f) throws IOException;
            }
    
            static class ChannelRw implements RW {
                final ByteBuffer directBuffer = ByteBuffer.allocateDirect(BLOCK_SIZE);
    
                @Override public String toString() {
                    return "Channel";
                }
    
                @Override public void write(File f) throws IOException {
                    FileChannel fc = new FileOutputStream(f).getChannel();
                    try {
                        for (int i = 0; i < blocks; i++) {
                            directBuffer.clear();
                            while (directBuffer.remaining() > 0) {
                                fc.write(directBuffer);
                            }
                        }
                    } finally {
                        fc.close();
                    }
                }
                @Override public long read(File f) throws IOException {
                    ByteBuffer buffer = ByteBuffer.allocateDirect(BLOCK_SIZE);
                    FileChannel fc = new FileInputStream(f).getChannel();
                    long checksum = 0;
                    try {
                        for (int i = 0; i < blocks; i++) {
                            buffer.clear();
                            while (buffer.hasRemaining()) {
                                fc.read(buffer);
                            }
                            buffer.flip();
                            while (buffer.hasRemaining()) {
                                buffer.get(acceptBuffer, 0, Math.min(acceptBuffer.length, buffer.remaining()));
                                checksum += acceptBuffer[acceptBuffer[0]];
                            }
                        }
                    } finally {
                        fc.close();
                    }
                    return checksum;
                }
            }
    
            static class StreamRw implements RW {
                final byte[] buffer = new byte[BLOCK_SIZE];
    
                @Override public String toString() {
                    return "Stream";
                }
    
                @Override public void write(File f) throws IOException {
                    FileOutputStream out = new FileOutputStream(f);
                    try {
                        for (int i = 0; i < blocks; i++) {
                            out.write(buffer);
                        }
                    } finally {
                        out.close();
                    }
                }
                @Override public long read(File f) throws IOException {
                    FileInputStream in = new FileInputStream(f);
                    long checksum = 0;
                    try {
                        for (int i = 0; i < blocks; i++) {
                            for (int remaining = acceptBuffer.length, read;
                                 (read = in.read(buffer)) != -1 && (remaining -= read) > 0; )
                            {
                                in.read(acceptBuffer, acceptBuffer.length - remaining, remaining);
                            }
                            checksum += acceptBuffer[acceptBuffer[0]];
                        }
                    } finally {
                        in.close();
                    }
                    return checksum;
                }
            }
    
    
            public static void purgeCache() throws IOException, InterruptedException {
                if (System.getProperty("os.name").startsWith("Mac")) {
                    new ProcessBuilder("sudo", "purge")
        //                    .inheritIO()
                            .start().waitFor();
                } else {
                    new ProcessBuilder("sudo", "su", "-c", "echo 3 > /proc/sys/vm/drop_caches")
        //                    .inheritIO()
                            .start().waitFor();
                }
            }
        }
    

    【讨论】:

      猜你喜欢
      • 2014-01-06
      • 1970-01-01
      • 1970-01-01
      • 2010-11-01
      • 2016-04-24
      • 1970-01-01
      • 2016-04-20
      • 1970-01-01
      • 2014-01-05
      相关资源
      最近更新 更多