Pertanyaan Benar-benar memaksa file sync / flush di Java


Bagaimana bisa data ditulis ke file sangat disiram / disinkronkan dengan perangkat blok oleh Java.

Saya mencoba kode ini dengan NIO:

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

Saya kira bahwa c.force (true) togehter dengan s.getFD (). Sync () seharusnya cukup karena doc untuk memaksa menyatakan

Memaksa pembaruan apa pun ke file saluran ini untuk ditulis ke perangkat penyimpanan yang memuatnya. Jika file saluran ini berada di perangkat penyimpanan lokal, maka ketika metode ini mengembalikan, dijamin bahwa semua perubahan yang dilakukan pada file tersebut sejak saluran ini dibuat, atau karena metode ini terakhir dipanggil, akan dituliskan ke perangkat itu. Ini berguna untuk memastikan bahwa informasi penting tidak hilang jika terjadi crash sistem.

Dokumentasi untuk sinkronisasi menyatakan:

Paksa semua buffer sistem untuk melakukan sinkronisasi dengan perangkat yang mendasarinya. Metode ini mengembalikan setelah semua data dan atribut yang dimodifikasi dari FileDescriptor ini telah ditulis ke perangkat yang relevan. Khususnya, jika FileDescriptor ini merujuk pada media penyimpanan fisik, seperti file dalam sistem file, sinkronisasi tidak akan kembali sampai semua salinan buffer yang di-memori yang terkait dengan FileDesecriptor ini telah ditulis ke media fisik. sync dimaksudkan untuk digunakan oleh kode yang membutuhkan penyimpanan fisik (seperti file) untuk berada dalam keadaan yang diketahui.

Kedua panggilan ini seharusnya cukup. Apakah itu? Saya kira mereka tidak.

Latar Belakang: Saya melakukan perbandingan kinerja kecil (2 GB, penulisan sekuensial) menggunakan C / Java dan versi Java dua kali lebih cepat dari versi C dan mungkin lebih cepat daripada perangkat keras (120 MB / dt pada satu HD). Saya juga mencoba menjalankan sinkronisasi tool baris perintah dengan Runtime.getRuntime (). Exec ("sync") tetapi itu tidak mengubah perilaku.

Kode C menghasilkan 70 MB / dtk (menggunakan API level rendah (buka, tulis, tutup) tidak banyak berubah):

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

Tanpa panggilan akhir untuk menyinkronkan; Saya mendapat nilai yang tidak realistis (lebih dari 1 GB alias kinerja memori utama).

Mengapa ada perbedaan besar antara C dan Jawa? Ada dua kemungkinan: Saya tidak menyinkronkan data dengan benar di Java atau kode C tidak optimal untuk beberapa alasan.

Memperbarui: Saya telah melakukan operasi strace dengan "strace -cfT cmd". Berikut hasilnya:

C (API Tingkat Rendah): MB / s 67.389782

% detik waktu panggilan / panggilan panggilan kesalahan syscall
------ ----------- ----------- --------- --------- ---- ------------
 87.21 0.200012 200012 1 fdatasync
 11,05 0,025345 1 32772 tulis
  1,74 0,004000 4000 1 sinkronisasi

C (API Tingkat Tinggi): MB / s 61.796458

% detik waktu panggilan / panggilan panggilan kesalahan syscall
------ ----------- ----------- --------- --------- ---- ------------
 73.19 0.144009 144009 1 sync
 26,81 0,052739 1 65539 tulis

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

% detik waktu panggilan / panggilan panggilan kesalahan syscall
------ ----------- ----------- --------- --------- ---- ------------
 80.07 105.387609 3215 32776 tulis
  2,58 3,390060 3201 1059 baca
  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 tulis
  0,00 0,000000 0 80 tutup

Nilai waktu tampaknya hanya waktu sistem dan karenanya tidak berarti.

Perbarui 2: Saya beralih ke server lain, reboot, dan saya menggunakan ext3 yang diformat segar. Sekarang saya hanya mendapat 4% perbedaan antara Java dan C. Saya tidak tahu apa yang salah. Terkadang hal-hal aneh. Saya seharusnya mencoba pengukuran dengan sistem lain sebelum menulis pertanyaan ini. Maaf.

Perbarui 3: Untuk meringkas jawabannya:

  • Gunakan c.force (true) diikuti oleh s.getFD (). Sync () untuk Java NIO dan s.flush () dan s.getFD (). Sync () untuk API aliran Java. Untuk API Tingkat Tinggi di C jangan lupa untuk menyinkronkan. Sebuah fflush mengirimkan data ke OS, tetapi tidak membawa data Anda ke perangkat blok.
  • Gunakan strace untuk menganalisis syscalls yang dilakukan oleh perintah
  • Cross periksa hasil Anda sebelum memposting pertanyaan.

Perbarui 4: Harap perhatikan tindak lanjut berikut pertanyaan.


32
2018-04-08 15:27


asal


Jawaban:


Anda perlu memberi tahu kami lebih banyak tentang perangkat keras dan sistem operasi, juga versi Java yang spesifik. Bagaimana Anda mengukur throughput ini?

Anda benar bahwa paksa / sinkronisasi harus memaksa data keluar ke media fisik.


Ini salinan versi mentah. Dikompilasi dengan gcc 4.0 pada Intel Mac, harus bersih.

/* 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
2018-04-08 15:47



Sebenarnya, di C Anda hanya ingin menelepon fsync() pada satu file deskriptor, tidak sync() (atau perintah "sync") yang memberi sinyal pada kernel flush semua buffer ke sistem disk-lebar.

Jika kamu strace (Mendapat Linux spesifik di sini) JVM Anda harus dapat mengamati suatu fsync() atau fdatasync() panggilan sistem yang dibuat pada file output Anda. Itu akan menjadi apa yang saya harapkan getFD().sync() panggilan untuk dilakukan. Saya berasumsi c.force(true) cukup bendera ke NIO itu fsync() harus dipanggil setelah setiap menulis. Mungkin saja JVM yang Anda gunakan tidak benar-benar mengimplementasikan sync() panggilan?

Saya tidak yakin mengapa Anda tidak melihat perbedaan apa pun saat memanggil "sync" sebagai perintah: tetapi jelas, setelah permintaan sinkronisasi pertama, yang berikutnya biasanya jauh lebih cepat. Sekali lagi, saya cenderung untuk keluar strace (truss pada Solaris) sebagai "apa yang sebenarnya terjadi di sini?" alat.


8
2018-04-08 18:48



Merupakan ide yang bagus untuk menggunakan penyelesaian integritas data I / O yang disinkronkan. Namun sampel C Anda menggunakan metode yang salah. Kau gunakan sync(), yang digunakan untuk menyinkronkan seluruh OS.

Jika Anda ingin menulis blok-blok file tunggal itu ke disk, Anda perlu menggunakannya fsync(2) atau fdatasync(2) di C. BTW: ketika Anda menggunakan buffered stdio di C (atau BufferedOutputStream atau beberapa Writer di Java), Anda perlu membersihkan keduanya terlebih dahulu sebelum Anda melakukan sinkronisasi.

Itu fdatasync() variannya sedikit lebih efisien jika file tidak mengubah nama atau ukuran sejak Anda melakukan sinkronisasi. Tetapi mungkin juga tidak semua data meta. Jika Anda ingin menulis sistem database aman transaksional Anda sendiri, Anda perlu mengamati beberapa hal lagi (seperti fsyncing direktori induk).


3
2018-04-08 15:36



Kode C bisa kurang optimal, karena menggunakan stdio daripada menulis OS mentah (). Tapi kemudian, java bisa lebih optimal karena mengalokasikan buffer yang lebih besar?

Bagaimanapun, Anda hanya bisa mempercayai APIDOC. Sisanya berada di luar tugas Anda.


0
2018-04-01 23:51



(Saya tahu ini adalah jawaban yang sangat terlambat, tetapi saya masuk ke dalam utas ini melakukan pencarian Google, dan itu mungkin bagaimana Anda juga berakhir di sini.)

Panggilan Anda sync () di Java pada deskriptor file tunggal, jadi hanya buffer yang terkait dengan satu file yang dikeluarkan ke disk.

Dalam C dan command-line, Anda memanggil sync () di seluruh sistem operasi - jadi setiap file buffer dialihkan ke disk, untuk semua yang dilakukan O / S Anda.

Agar sebanding, panggilan C harus ke syncfs (fp);

Dari halaman manual 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.

0