6

使用以下代码作为基准,系统可以在几分之一秒内将 10,000 行写入磁盘:

void withSync() {
    int f = open( "/tmp/t8" , O_RDWR | O_CREAT );
    lseek (f, 0, SEEK_SET );
    int records = 10*1000;
    clock_t ustart = clock();
    for(int i = 0; i < records; i++) {
        write(f, "012345678901234567890123456789" , 30);
        fsync(f);
    }
    clock_t uend = clock();
    close (f);
    printf("   sync() seconds:%lf   writes per second:%lf\n", ((double)(uend-ustart))/(CLOCKS_PER_SEC), ((double)records)/((double)(uend-ustart))/(CLOCKS_PER_SEC));
}

在上面的代码中,10,000 条记录可以在几分之一秒内写入并刷新到磁盘,输出如下:

sync() seconds:0.006268   writes per second:0.000002

在 Java 版本中,写入 10,000 条记录需要 4 秒以上。这只是Java的限制,还是我遗漏了什么?

public void testFileChannel() throws IOException {
    RandomAccessFile raf = new RandomAccessFile(new File("/tmp/t5"),"rw");
    FileChannel c = raf.getChannel();
    c.force(true);
    ByteBuffer b = ByteBuffer.allocateDirect(64*1024);
    long s = System.currentTimeMillis();
    for(int i=0;i<10000;i++){            
        b.clear();
        b.put("012345678901234567890123456789".getBytes());
        b.flip();
        c.write(b);
                    c.force(false);
    }
    long e=System.currentTimeMillis();
    raf.close();
    System.out.println("With flush "+(e-s));

}

返回这个:

With flush 4263

请帮助我了解用 Java 将记录写入磁盘的正确/最快方法是什么。

注意:我将RandomAccessFile类与 a 结合使用,ByteBuffer因为最终我们需要对该文件进行随机读/写访问。

4

4 回答 4

5

实际上,我很惊讶测试并没有变慢。force 的行为取决于操作系统,但从广义上讲,它会将数据强制写入磁盘。如果您有 SSD,您可能会实现每秒 40K 的写入,但使用 HDD 则不会。在 C 示例中,它显然没有将数据提交到磁盘,因为即使是最快的 SSD 也无法执行超过 235K IOPS(制造商保证它不会比这更快:D)

如果您每次都需要将数据提交到磁盘,您可以预期它会很慢并且完全取决于硬件的速度。如果您只需要将数据刷新到操作系统,并且如果程序崩溃但操作系统没有,您不会丢失任何数据,您可以无需强制写入数据。更快的选择是使用内存映射文件。这将为您提供随机访问,而无需对每条记录进行系统调用。

我有一个Java Chronicle库,它每秒可以读取/写入 5-20 百万条记录,延迟为 80 ns,采用随机访问的文本或二进制格式,并且可以在进程之间共享。这只工作这么快,因为它不会在每条记录上将数据提交到磁盘,但您可以测试如果 JVM 在任何时候崩溃,写入编年史的数据不会丢失。

于 2012-11-09T07:32:16.510 回答
1

这段代码更类似于你用 C 编写的代码。在我的机器上只需要 5 毫秒。如果您确实需要在每次写入后刷新,则大约需要 60 毫秒。您的原始代码在这台机器上花费了大约 11 秒。顺便说一句,关闭输出流也会刷新。

public static void testFileOutputStream() throws IOException {
  OutputStream os = new BufferedOutputStream( new FileOutputStream( "/tmp/fos" ) );
  byte[] bytes = "012345678901234567890123456789".getBytes();
  long s = System.nanoTime();
  for ( int i = 0; i < 10000; i++ ) {
    os.write( bytes );
  }
  long e = System.nanoTime();
  os.close();
  System.out.println( "outputstream " + ( e - s ) / 1e6 );
}
于 2012-11-09T08:04:10.170 回答
0

我认为这与您的 C 版本最相似。我认为您的 java 示例中的直接缓冲区导致的缓冲区副本比 C 版本多得多。这在我的(旧)盒子上大约需要 2.2 秒。

  public static void testFileChannelSimple() throws IOException {
    RandomAccessFile raf = new RandomAccessFile(new File("/tmp/t5"),"rw");
    FileChannel c = raf.getChannel();
    c.force(true);
    byte[] bytes = "012345678901234567890123456789".getBytes();
    long s = System.currentTimeMillis();
    for(int i=0;i<10000;i++){
      raf.write(bytes);
      c.force(true);
    }
    long e=System.currentTimeMillis();
    raf.close();
    System.out.println("With flush "+(e-s));
  }
于 2012-11-12T02:48:50.877 回答
0

fputs 的 Java 等价物是file.write("012345678901234567890123456789");,您正在调用 4 个函数,而 C 中只有 1 个,延迟似乎很明显

于 2012-11-09T06:08:42.563 回答