代码之家  ›  专栏  ›  技术社区  ›  dmeister

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

  •  33
  • dmeister  · 技术社区  · 15 年前

    如何将数据写入文件 真的? 用Java来刷新/同步块设备。

    我用NIO尝试了这个代码:

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

    我认为c.force(true)和s.getfd().sync()应该足够了,因为 force 状态

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

    文件 sync 国家:

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

    这两个电话就足够了。它是?我想他们不是。

    背景:我使用C/Java做了一个小的性能比较(2 GB,顺序写),Java版本是C版本的两倍,并且可能比硬件快(在一个HD上120 Mb/s)。我还尝试执行命令行工具sync with runtime.getruntime().exec(“sync”),但这并没有改变行为。

    产生70MB/s的C代码是(使用低级API(打开、写入、关闭)不会改变太多):

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

    没有最后的同步调用;我得到了不现实的值(超过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,JavaIO API): MB/秒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,JavaNIOAPI): MB/秒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: 总结答案:

    • 对Java的流API使用C.FULL(TRUE)和JavaNIO和S.FrHuSH()和S.GETFF()(Syc())。对于C中的高级API,不要忘记同步。fflush将数据提交给操作系统,但不会将数据带到块设备。
    • 使用strace分析命令执行的系统调用
    • 在发布问题之前交叉检查您的结果。

    更新4: 请注意以下跟进 question .

    5 回复  |  直到 9 年前
        1
  •  2
  •   Charlie Martin    15 年前

    你需要告诉我们更多关于硬件和操作系统,还有具体的Java版本。你是如何衡量这个吞吐量的?

    正确的做法是,强制/同步应将数据强制输出到物理媒体。


    这是原版的拷贝。在IntelMac上用GCC4.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);
    }
    
        2
  •  8
  •   Arunprasanth K V    9 年前

    实际上,在C语言中你只想打电话 fsync() 在一个文件描述符上,不是 sync() (或“同步”命令),它向内核发送信号 flush 所有缓冲区到磁盘系统范围。

    如果你 strace (此处是特定于Linux的)您应该能够观察到 fSyc() fdatasync() 正在对输出文件进行系统调用。这就是我所期望的 getFD() . 同步() 打电话去做。我假设 c.force(true) 只需标记到NIO fSyc() 每次写入后都应调用。可能只是您使用的JVM实际上并没有实现 同步() 打电话?

    我不知道为什么在将“sync”作为命令调用时看不到任何区别:但显然,在第一次同步调用之后,后续的调用通常要快得多。再说一次,我倾向于爆发 斯特雷斯 (truss on solaris)作为“这里到底发生了什么?”工具。

        3
  •  3
  •   eckes    10 年前

    最好使用同步I/O数据完整性完成。但是,您的C样本使用的方法不正确。你用 sync() ,用于同步整个操作系统。

    如果要将单个文件的块写入磁盘,则需要使用 fsync(2) fdatasync(2) 在C. BTW中:当你在C中使用缓冲的STDIO(或者BuffReDeExtStudio流或爪哇的某个作者)时,在同步之前,首先需要刷新两者。

    这个 fdatasync() 如果文件自同步后未更改名称或大小,则variant的效率会提高一点。但它也可能无法传递所有的元数据。如果您想编写自己的事务安全数据库系统,则需要观察更多的内容(如fsynching父目录)。

        4
  •  0
  •   Ingo    15 年前

    C代码可能是次优的,因为它使用stdio而不是raw os write()。但是,Java可以更优化,因为它分配更大的缓冲区?

    不管怎样,你只能相信Apidoc。剩下的超出了你的职责范围。

        5
  •  0
  •   Adam Fanello    10 年前

    (我知道这是一个非常晚的回复,但我在谷歌搜索时遇到了这个线索,这可能也是你在这里的结局。)

    您在爪哇上的调用Syc()在单个文件描述符上,因此只有与该文件相关的缓冲区才被刷新到磁盘。

    在C和命令行中,您在整个操作系统上调用sync(),所以每个文件缓冲区都会被清空到磁盘上,因为您的O/S正在做什么。

    要进行比较,C调用应该是到syncfs(fp);

    从Linux手册页:

       sync() causes all buffered modifications to file metadata and data to
       be written to the underlying file systems.
    
       syncfs() is like sync(), but synchronizes just the file system contain‐
       ing file referred to by the open file descriptor fd.