ファイル内のある位置を探して少量のデータ(20バイト)を書き込むと、舞台裏で何が起こりますか?
私の理解
私の知る限り、ディスクからの書き込みまたは読み取りが可能なデータの最小単位は1セクターです(従来は512バイトでしたが、その標準は現在変更されています)。つまり、20バイトを書き込むには、セクター全体を読み取り、メモリ内の一部を変更して、ディスクに書き戻す必要があります。
これは、バッファリングされていないI/Oで発生すると予想されることです。また、バッファリングされたI / Oもほぼ同じことを実行することを期待していますが、そのキャッシュについては賢明です。したがって、ランダムなシークと書き込みを実行してローカリティをウィンドウから吹き飛ばすと、バッファリングされたI/OとバッファリングされていないI/Oの両方で同様のパフォーマンスが得られるはずです...おそらく、バッファリングされていない方がわずかに優れています。
繰り返しになりますが、バッファリングされたI / Oが1つのセクターのみをバッファリングするのはおかしいので、それがひどく実行されることも期待できます。
私のアプリケーション
10万ポイント以上のリモートテレメトリを受信するSCADAデバイスドライバによって収集された値を保存しています。各レコードが40バイトになるようにファイルに余分なデータがありますが、更新中に書き込む必要があるのはそのうちの20バイトだけです。
実装前のベンチマーク
見事に過剰に設計されたソリューションを思いつく必要がないことを確認するために、合計200,000レコードを含む可能性のあるファイルに書き込まれた数百万のランダムレコードを使用してテストを実行しました。各テストは、公平であるために同じ値で乱数ジェネレーターをシードします。まず、ファイルを消去して全長(約7.6メガバイト)までパディングし、次に数百万回ループして、ランダムなファイルオフセットと一部のデータを2つのテスト関数のいずれかに渡します。
void WriteOldSchool( void *context, long offset, Data *data )
{
int fd = (int)context;
lseek( fd, offset, SEEK_SET );
write( fd, (void*)data, sizeof(Data) );
}
void WriteStandard( void *context, long offset, Data *data )
{
FILE *fp = (FILE*)context;
fseek( fp, offset, SEEK_SET );
fwrite( (void*)data, sizeof(Data), 1, fp );
fflush(fp);
}
多分驚きはありませんか?
方法は一番OldSchool
上に出てきました-たくさん。6倍以上高速でした(1秒あたり232000レコードに対して148万)。ハードウェアキャッシュに遭遇していないことを確認するために、データベースサイズを2,000万レコード(ファイルサイズ763メガバイト)に拡張し、同じ結果を得ました。
の明白な呼び出しを指摘する前にfflush
、それを削除しても効果がなかったと言わせてください。これは、十分に離れた場所を探すときにキャッシュをコミットする必要があるためだと思います。これは、ほとんどの場合に行っていることです。
どうしたの?
バッファリングされたI/Oは、書き込もうとするたびに、ファイルの大きなチャンクを読み取っている(場合によってはすべてを書き込んでいる)必要があるように思われます。私はそのキャッシュをほとんど利用していないので、これは非常に無駄です。
さらに(ディスク上のハードウェアキャッシュの詳細はわかりませんが)、バッファリングされたI / Oが1つだけ変更したときに多数のセクターを書き込もうとすると、ハードウェアキャッシュの効率が低下します。
私の実験結果よりもこれをコメントして説明できるディスクの専門家はいますか?=)