真的在Java中强制文件同步/刷新

dme*_*ter 36 java storage

如何通过Java 真正使用块设备刷新/同步写入文件的数据.

我用NIO试过这段代码:

FileOutputStream s = new FileOutputStream(filename)
Channel c = s.getChannel()
while(xyz)
    c.write(buffer)
c.force(true)
s.getFD().sync()
c.close()
Run Code Online (Sandbox Code Playgroud)

我认为c.force(true)与s.getFD()同步.sync()应该足够了,因为强制状态的文档

强制将此通道文件的任何更新写入包含它的存储设备.如果此通道的文件驻留在本地存储设备上,则当此方法返回时,保证自创建此通道以来对文件所做的所有更改,或者自上次调用此方法以来,该文件都将写入该设备.这对于确保在系统崩溃时不会丢失关键信息非常有用.

同步状态的文档:

强制所有系统缓冲区与底层设备同步.在将此FileDescriptor的所有已修改数据和属性写入相关设备之后,此方法返回.特别是,如果此FileDescriptor引用物理存储介质(例如文件系统中的文件),则在将与此FileDesecriptor关联的缓冲区的所有内存中修改副本写入物理介质之前,不会返回sync.sync意味着需要物理存储(例如文件)处于已知状态的代码.

这两个电话应该足够了.是吗?我猜他们不是.

背景:我使用C/Java进行小的性能比较(2 GB,顺序写入),Java版本的速度是C版本的两倍,可能比硬件速度快(单个HD上的速度为120 MB/s).我还尝试使用Runtime.getRuntime().exec("sync")执行命令行工具同步,但这并没有改变行为.

导致70 MB/s的C代码(使用低级API(打开,写入,关闭)不会发生太大变化):

FILE* fp = fopen(filename, "w");
while(xyz) {
    fwrite(buffer, 1, BLOCK_SIZE, fp);
}
fflush(fp);
fclose(fp);
sync();
Run Code Online (Sandbox Code Playgroud)

没有最后的同步调用; 我得到了不切实际的价值(超过1 GB又称主内存性能).

为什么C和Java之间有这么大的差异?有两种可能性:我没有在Java中正确地同步数据,或者C代码由于某种原因是次优的.

更新:我已经使用"strace -cfT cmd"完成了strace运行.结果如下:

C(低级API):MB/s 67.389782

% time     seconds  usecs/call     calls    errors syscall
------ ----------- ----------- --------- --------- ----------------
 87.21    0.200012      200012         1           fdatasync
 11.05    0.025345           1     32772           write
  1.74    0.004000        4000         1           sync

C(高级API):MB/s 61.796458

% time     seconds  usecs/call     calls    errors syscall
------ ----------- ----------- --------- --------- ----------------
 73.19    0.144009      144009         1           sync
 26.81    0.052739           1       65539           write

Java(1.6 SUN JRE,java.io API):MB/s 128.6755466197537

% time     seconds  usecs/call     calls    errors syscall
------ ----------- ----------- --------- --------- ----------------
 80.07  105.387609        3215     32776           write
  2.58    3.390060        3201      1059           read
  0.62    0.815251      815251         1           fsync

Java(1.6 SUN JRE,java.nio API):MB/s 127.45830221558376

  5.52    0.980061      490031         2           fsync
  1.60    0.284752           9     32774           write
  0.00    0.000000           0        80           close

时间值似乎只是系统时间,因此毫无意义.

更新2:我切换到另一台服务器,重新启动,我使用新格式化的ext3.现在我在Java和C之间只有4%的差异.我只是不知道出了什么问题.有时事情很奇怪.在写这个问题之前,我应该尝试用另一个系统进行测量.抱歉.

更新3:总结答案:

  • 使用c.force(true)后跟s.getFD().sync()用于Java NIO和s.flush()和s.getFD().sync()用于Java的流API.对于C语言中的高级API,请不要忘记同步.fflush将数据提交给操作系统,但不会将数据带到块设备.
  • 使用strace分析命令完成的系统调用
  • 在发布问题之前交叉检查您的结果.

更新4:请注意以下后续问题.

ara*_*nid 9

实际上,在C中你只想调用fsync()一个文件描述符,而不是sync()(或"sync"命令),它将内核通知flush所有缓冲区到系统范围的磁盘.

如果你strace(让Linux特有这里)的JVM,你应该能够观察到一个fsync()fdatasync()系统调用正在对你的输出文件进行.那将是我所期待的getFD().sync()打电话去做.我假设c.force(true)只是fsync()在每次写入后应该调用的NIO标志.可能只是你正在使用的JVM实际上没有实现sync()调用?

我不确定为什么在将"sync"作为命令调用时没有看到任何区别:但显然,在第一次同步调用之后,后续的调用通常要快得多.再一次,我倾向于突破strace(Solaris上的桁架)作为"这里发生了什么?" 工具.


eck*_*kes 5

使用同步I / O数据完整性完成是一个好主意。但是,您的C示例使用了错误的方法。您使用sync(),它用于同步整个操作系统。

如果要将单个文件的块写入磁盘,则需要使用fsync(2)fdatasync(2)在C中使用。BTW:在C中使用缓冲的stdio(或Java中的BufferedOutputStream或某些Writer)时,需要先刷新两者,然后再进行同步。

fdatasync()变种是一个比较有效的,如果因为你同步的文件并没有改变名称或大小。但是它也可能不会保留所有元数据。如果要编写自己的事务安全数据库系统,则需要观察更多内容(例如,同步父目录)。


Cha*_*tin 1

您需要告诉我们更多有关硬件和操作系统的信息,以及具体的 Java 版本。您如何衡量这个吞吐量?

您是正确的,强制/同步应该将数据强制输出到物理介质。


这是副本的原始版本。在 Intel Mac 上用 gcc 4.0 编译,应该是干净的。

/* rawcopy -- pure C, system calls only, copy argv[1] to argv[2] */

/* This is a test program which simply copies from file to file using
 * only system calls (section 2 of the manual.)
 *
 * Compile:
 *
 *      gcc -Wall -DBUFSIZ=1024 -o rawcopy rawcopy.c
 *
 * If DIRTY is defined, then errors are interpreted with perror(3).
 * This is ifdef'd so that the CLEAN version is free of stdio.  For
 * convenience I'm using BUFSIZ from stdio.h; to compile CLEAN just
 * use the value from your stdio.h in place of 1024 above.
 *
 * Compile DIRTY:
 *
 *      gcc -DDIRTY -Wall -o rawcopy rawcopy.c
 *
 */
#include <fcntl.h>
#include <sys/types.h>
#include <sys/uio.h>
#include <stdlib.h>
#include <unistd.h>
#if defined(DIRTY)
#   if defined(BUFSIZ)
#       error "Don't define your own BUFSIZ when DIRTY"
#   endif
#   include <stdio.h>
#   define PERROR perror(argv[0])
#else
#   define CLEAN
#   define PERROR
#   if ! defined(BUFSIZ)
#       error "You must define your own BUFSIZ with -DBUFSIZ=<number>"
#   endif
#endif

char * buffer[BUFSIZ];          /* by definition stdio BUFSIZ should
                                   be optimal size for read/write */

extern int errno ;              /* I/O errors */

int main(int argc, char * argv[]) {
    int fdi, fdo ;              /* Input/output file descriptors */
    ssize_t len ;               /* length to read/write */
    if(argc != 3){
        PERROR;
        exit(errno);
    }

    /* Open the files, returning perror errno as the exit value if fails. */
    if((fdi = open(argv[1],O_RDONLY)) == -1){
        PERROR;
        exit(errno);
    }
    if((fdo = open(argv[2], O_WRONLY|O_CREAT)) == -1){
        PERROR;
        exit(errno);
    }

    /* copy BUFSIZ bytes (or total read on last block) fast as you
       can. */
    while((len = read(fdi, (void *) buffer, BUFSIZ)) > -1){
        if(len == -1){
            PERROR;
            exit(errno);
        }
        if(write(fdo, (void*)buffer, len) == -1){
            PERROR;
            exit(errno);
        }
    }
    /* close and fsync the files */
    if(fsync(fdo) ==-1){
        PERROR;
        exit(errno);
    }
    if(close(fdo) == -1){
        PERROR;
        exit(errno);
    }
    if(close(fdi) == -1){
        PERROR;
        exit(errno);
    }

    /* if it survived to here, all worked. */
    exit(0);
}
Run Code Online (Sandbox Code Playgroud)