web-dev-qa-db-ja.com

本当にファイルの同期/フラッシュを強制的にJava

ファイルreallyに書き込まれたデータをJavaによってブロックデバイスとフラッシュ/同期するにはどうすればよいですか。

私はNIOでこのコードを試しました:

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

force のドキュメントには、c.force(true)togehterとs.getFD()。sync()で十分であると思いました。

このチャネルのファイルへの更新を、それを含むストレージデバイスに強制的に書き込みます。このチャネルのファイルがローカルストレージデバイスにある場合、このメソッドが返されると、このチャネルが作成されてから、またはこのメソッドが最後に呼び出されてからファイルに加えられたすべての変更がそのデバイスに書き込まれることが保証されます。これは、システムがクラッシュした場合に重要な情報が失われないようにするために役立ちます。

sync のドキュメントには次のように記載されています。

すべてのシステムバッファを強制的に基盤となるデバイスと同期させます。このメソッドは、このFileDescriptorのすべての変更されたデータと属性が関連するデバイスに書き込まれた後に返されます。特に、このFileDescriptorがファイルシステム内のファイルなどの物理ストレージメディアを参照している場合、このFileDesecriptorに関連付けられているバッファのメモリ内で変更されたすべてのコピーが物理メディアに書き込まれるまで同期は返されません。 syncは、物理ストレージ(ファイルなど)が既知の状態である必要があるコードで使用されることを目的としています。

これらの2つの呼び出しで十分です。それは...ですか?そうではないと思います。

背景:C/Javaを使用して小さなパフォーマンス比較(2 GB、順次書き込み)を行い、JavaバージョンはCバージョンの2倍高速で、おそらくハードウェア(120 MB/s単一のHDで)。Runtime.getRuntime()。exec( "sync")を使用してコマンドラインツールsyncを実行しようとしましたが、動作は変わりませんでした。

70 MB/sになるCコードは次のとおりです(低レベルAPI(open、write、close)を使用してもあまり変わりません):

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

同期するための最後の呼び出しなし。非現実的な値(1 GB以上のメインメモリパフォーマンス)が得られました。

なぜCとJavaの間にこんなに大きな違いがあるのですか? 2つの可能性があります:Javaでデータを正しく同期しないか、何らかの理由でCコードが最適ではありません。

更新:「strace-cfTcmd」を使用してstraceを実行しました。結果は次のとおりです。

C(低レベルAPI):MB/s 67.389782

%時間秒usecs/call呼び出しエラーsyscall 
 ------ ----------- ----------- --- ------ --------- ---------------- 
 87.21 0.200012 200012 1 fdatasync 
 11.05 0.025345 1 32772書き込み
 1.74 0.004000 40001同期

C(高水準API):MB/s 61.796458

%時間秒usecs/call呼び出しエラーsyscall 
 ------ ----------- ----------- --- ------ --------- ---------------- 
 73.19 0.144009 1440091同期
 26.81 0.052739 1 65539書く

Java(1.6 Sun JRE、Java.io API):MB/s 128.6755466197537

%時間秒usecs/call呼び出しエラーsyscall 
 ------ ----------- ----------- --- ------ --------- ---------------- 
 80.07 105.387609 321532776書き込み
 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:答えを要約するには:

  • Java NIOおよびs.flush()の場合はc.force(true)、続いてs.getFD()。sync()を使用し、JavaのストリームAPIの場合はs.getFD()。sync()を使用します.Cの高レベルAPIの場合、同期することを忘れないでください。フラッシュはデータをOSに送信しましたが、データをブロックデバイスに送信しません。
  • Straceを使用して、コマンドによって実行されたシステムコールを分析します
  • 質問を投稿する前に、結果をクロスチェックしてください。

更新4:次のフォローアップに注意してください 質問

36
dmeister

ハードウェアとオペレーティングシステム、および特定のJavaバージョンについて詳しく教えてください。このスループットをどのように測定していますか?

Force/syncがデータを物理メディアに強制的に出力する必要があるのは正しいです。


これがコピーの生バージョンです。 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
Charlie Martin

実際、Cでは、カーネルにflushすべてを通知するfsync()(または「sync」コマンド)ではなく、1つのファイル記述子でsync()を呼び出すだけです。システム全体のディスクへのバッファ。

JVMをstrace(ここでLinux固有のものにする)の場合、出力ファイルで行われているfsync()またはfdatasync()システムコールを監視できるはずです。それは私がgetFD().sync()呼び出しに期待することです。 c.force(true)は、書き込みのたびにfsync()を呼び出す必要があることをNIOに通知するだけだと思います。使用しているJVMが実際にsync()呼び出しを実装していない可能性がありますか?

コマンドとして「sync」を呼び出したときに違いが見られなかった理由はわかりませんが、明らかに、最初の同期呼び出しの後、後続の呼び出しは通常、はるかに高速です。繰り返しになりますが、私はstrace(Solarisのトラス)を「ここで実際に何が起こっているのか」として説明したいと思います。ツール。

9
araqnid

同期I/Oデータ整合性補完を使用することをお勧めします。ただし、Cサンプルは間違った方法を使用しています。 OS全体を同期するために使用されるsync()を使用します。

その単一ファイルのブロックをディスクに書き込みたい場合は、Cでfsync(2)またはfdatasync(2)を使用する必要があります。ところで:Cでバッファリングされたstdio(またはBufferedOutputStreamまたはJavaの一部のWriter)同期する前に、最初に両方をフラッシュする必要があります。

同期してからファイルの名前やサイズが変更されていない場合、fdatasync()バリアントの方が少し効率的です。ただし、すべてのメタデータを永続化できるとは限りません。独自のトランザクションセーフデータベースシステムを作成する場合は、さらにいくつかのことを観察する必要があります(親ディレクトリの同期など)。

5
eckes

Cコードは、生のOS write()ではなくstdioを使用するため、最適ではない可能性があります。しかし、Javaは、より大きなバッファを割り当てるため、より最適である可能性がありますか?

とにかく、あなたはAPIDOCだけを信頼することができます。残りはあなたの義務を超えています。

0
Ingo

(これは非常に遅い返信であることを私は知っています、しかし私はグーグル検索をしているこのスレッドに出くわしました、そしてそれはおそらくあなたがここにたどり着いた方法です。)

単一のファイル記述子でJavaのsync()を呼び出すと、その1つのファイルに関連するバッファーのみがディスクにフラッシュされます。

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.
0
Adam Fanello