20

ファイル内のある位置を探して少量のデータ(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つだけ変更したときに多数のセクターを書き込もうとすると、ハードウェアキャッシュの効率が低下します。

私の実験結果よりもこれをコメントして説明できるディスクの専門家はいますか?=)

4

2 に答える 2

5

C標準ライブラリ関数は追加のバッファリングを実行し、通常、ランダムIOではなくストリーミング読み取り用に最適化されています。私のシステムでは、JameySharpが見たスプリアス読み取りは観察されません。オフセットがページサイズに揃えられていない場合にのみスプリアス読み取りが表示されます。Cライブラリは常にIOバッファーを4kbに揃えようとしている可能性があります。なにか。

あなたの場合、適度に小さなデータセット全体でランダムな読み取りと書き込みをたくさん行う場合は、pread/pwriteを使用してシステムコールを探す必要がないようにするか、単にmmapデータセットをメモリに書き込んで書き込むのが最適です(データセットがメモリに収まる場合は、最速になる可能性があります)。

于 2012-11-01T06:32:36.223 に答える
5

実際、少なくともGNU libcを使用している私のシステムでは、stdioが変更された部分を書き戻す前に4kBブロックを読み取っているように見えます。私には偽物のようですが、当時は誰かがそれは良い考えだと思っていたと思います。

簡単なCプログラムを作成してファイルを開き、少量のデータを1回書き込んで、終了することで確認しました。次に、straceで実行して、実際にトリガーされたシステムコールを確認しました。10000のオフセットで書いていると、私はこれらのシステムコールを見ました:

lseek(3, 8192, SEEK_SET)                = 8192
read(3, "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 1808) = 1808
write(3, "hello", 5)                    = 5

このプロジェクトでは、低レベルのUnixスタイルのI/Oを使い続けたいと思われますね。

于 2012-11-01T05:12:52.610 に答える