2009-04-08 17 views
29

ファイルにデータを書き込むにはどうすれば本当にをJavaでブロックデバイスとフラッシュ/同期させることができますか?Javaで本当にファイルの同期/フラッシュを実行する

IはNIOを使用してこのコードを試してみました:同期()I)はs.getFD(とtogehterそのc.force(true)を想定

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

force状態

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

ドキュメント

syncの状態:すべてのシステム・バッファ

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

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

背景C/Javaを使用した小さなパフォーマンス比較(2GB、シーケンシャル書き込み)を行い、JavaバージョンはCバージョンの2倍の速さで、おそらくハードウェアよりも高速です(1台のHD上で120MB /秒)。私はRuntime.getRuntime()。exec( "sync")を使ってコマンドラインツールの同期を実行しようとしましたが、動作を変更していません。

70メガバイト/秒で得られたCコードは、()(書き込み、オープン近い低レベルAPIを使用すると、あまり変化しない)である:

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 -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、API java.io): MB/s 1286755466197537

 
% 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。s.getFD(続く

  • 使用c.force(真の))は、Java NIOとs.flushの同期()()とs.getFD ().sync()をJavaのストリームAPIに使用します。 CのハイレベルAPIについては、同期することを忘れないでください。 fflushがデータをOSに提出しましたが、データをブロックデバイスに持ち込むことはありません。
  • コマンドで実行されたシステムコールを分析するにはstraceを使用してください。
  • 質問を投稿する前に結果をクロスチェックしてください。

更新4: 以下のフォローアップ​​3210に注意してください。

+0

私は実際にセクション2の関数を使用してスループットを見たいと思います。 –

+0

BLOCK_SIZEには何を使用していますか? Javaのバッファと同じサイズですか?最近では512が非常に最適ではないようです。おそらく、少なくとも4096(x86ではページサイズ)以上、あるいはそれ以上のものが必要でしょう。私はいくつかのマシンで32kまで測定可能な改善を見ました。もちろん、バッファがページアライメントされていると、カーネルに最適化の余地が広がります。 – aij

+0

もう1つの問題は、投稿したコードが「低レベルのAPI(公開、書き込み、終了)」を使用していないことです。それは、より高いレベルの移植可能なstdio API(fopen、fwrite、fclose)を使用しています。これは、デフォルトで追加のバッファリング層を追加します。投稿したコードの外で明示的にバッファリングをオフにしましたか? – aij

答えて

2

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

強制/同期で物理メディアにデータを強制的に送信する必要があります。


ここにはコピーの生バージョンがあります。インテル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); 
} 
+0

IcedTea OpenJDK 1.6 Java、openSUSE 11 Linux、JBODからの4つのコアCPU、4 GB、1つのSATA-HDファイバチャネル。 – dmeister

+0

同じランダムデータの64Kブロックを使用して4 GBのファイルを書き、ファイルオープンとファイルクローズの間の時間を測定しました。 – dmeister

+0

他の作業負荷ですか? CはGCC> 4でしたか?その設定は、私がSTK(RIP)と120 MB/sで試したものに似ています。 –

0

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

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

6

は実際には、Cであなただけのディスクシステム全体にflushにすべてのバッファをカーネルに信号を一つのファイルディスクリプタ、ないsync()(または「同期」コマンド)にfsync()を呼びたいです。

もしあなたの出力ファイルにfsync()またはfdatasync()システムコールが行われているのを観測できるはずです。strace(ここではLinux固有のものです)。それは私がgetFD()を期待しているものになります。 sync() doを呼び出します。私はそれぞれの書き込みの後にfsync()が呼び出されるべきであることをNIOへの単純なフラグc.force(true)と仮定します。使用しているJVMが実際にsync()呼び出しを実装していないことがありますか?

"sync"をコマンドとして呼び出すときに何の違いが見られなかったのか分かりませんが、最初の同期呼び出しの後では、通常はかなり高速です。再び、私はstrace(Solaris上のトラス)を "実際に何が起こっているのか?"と突き止める傾向があります。ツール。

+0

システムコールをトレースするという考えは良いです。私は明日それをします。 – dmeister

+0

force()はfsyncまたはfdatasyncを呼び出します(メタデータフラグに応じて)。ただし、各呼び出しの直後にfsync/fdatasyncを呼び出すように状態を設定することはありません。私はOpenJDKのソースコードでそれを調べました。 – dmeister

0

(私はこれは非常に返事が遅れているけど、私はGoogleの検索を行うこのスレッドに走った、そしてそれはあなたもここに終わったか、おそらくです。)単一のJavaで

あなたの呼び出し元の同期()そのファイルに関連するバッファだけがディスクにフラッシュされます。

Cとコマンドラインでは、オペレーティングシステム全体でsync()を呼び出しています。そのため、O/Sが実行しているすべてのファイルバッファがディスクにフラッシュされます。

これに匹敵するように、Cの呼び出しはsyncfs(fp)にする必要があります。 Linuxのmanページから

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. 
+1

syncfs()はsync()より良くないので、どちらも間違っています。 fdatasync()の呼び出しは、Javaが使用する呼び出しと、Cで使用する呼び出しです。 – eckes

2

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

もしその単一ファイルのブロックをディスクに書きたいなら、Cでfsync(2)またはfdatasync(2)を使う必要があります。BTW:Cでバッファリングされたstdio(あるいはBufferedOutputStreamやJavaのWriter)を使うときは、同期する前に両方を先にフラッシュしてください。

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

関連する問題