4

我有一个 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);
...
4

4 回答 4

7

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

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

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

于 2015-06-28T00:17:58.423 回答
4

从这篇文章中可以看出,旧版 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 相提并论。可以排除垃圾收集 :)

于 2015-06-28T00:06:23.407 回答
1

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

尝试使用更快的东西,比如 aBufferedInputStream或 a BufferedOutputStream,看看你能得到什么速度。

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

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

于 2015-06-27T23:01:51.423 回答
1

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

于 2015-06-28T23:04:01.017 回答