大きなバイナリ ファイル (2 ~ 3 GB) を 30 バイトごとに読み取る最速の方法は何ですか? I/O バッファが原因で fseek にパフォーマンス上の問題があることを読みましたが、30 バイトごとに取得する前に 2 ~ 3 GB のデータをメモリに読み込みたくありません。
7 に答える
私がお勧めするのは、数千バイトのバッファを作成し、そこから 30 バイトごとに読み取り、次の数千バイトでバッファをリロードし、eof に到達するまで続行することです。そうすれば、メモリに読み込まれるデータの量が制限され、ファイルから頻繁に読み取る必要もなくなります。作成するバッファが大きいほど、高速になることがわかります。
編集:実際には、以下に提案するように、バッファを数千バイトではなく数百kbにしたいでしょう(私が言ったように、バッファが大きいほどファイルの読み取りが速くなります)。
1 バイトを読み取ってから、ループ内で 29 バイトをシークできます。ただし、IO サブシステムはファイルからセクター単位で読み取る必要があり、通常は 512 バイトのサイズであるため、最終的にはファイル全体を読み取ることになります。
長期的には、ファイル全体をステップサイズの倍数のチャンクで読み取り、バッファを調べる方が高速です。バッファー サイズが 30 の倍数であることを確認すると、作業が少し簡単になります。また、512 の倍数である場合は、fileio サブシステムの作業が楽になります。
while (still more file to read)
{
char buf[30 * 512];
int cread = fread (buf, sizeof(buf), 1, fd);
for (int ii = 0; ii < cread; ii += 30)
{
}
}
これは非効率に見えるかもしれませんが、30 バイトのチャンクを読み取ろうとするよりも高速であることがわかります。
ところで。Windows で実行していて、OS 固有にする意思がある場合は、メモリ マップ ファイルのパフォーマンスに勝るものはありません。 ディスク上の非常に巨大なファイルをスキャンする方法は?
ANSI-C から抜け出し、OS 固有の呼び出しを使用する場合は、メモリ マップ ファイルを使用することをお勧めします。これは Posix バージョンです (Windows には独自の OS 固有の呼び出しがあります)。
#define MAPSIZE 4096
int fd = open(file, O_RDONLY);
struct stat stbuf;
fstat(fd, &stbuf);
char *addr = 0;
off_t last_mapped_offset = -1;
off_t idx = 0;
while (idx < stbuf.st_size)
{
if (last_mapped_offset != (idx / MAPSIZE))
{
if (addr)
munmap(addr, MAPSIZE);
last_mapped_offset = idx / MAPSIZE;
addr = mmmap(0, MAPSIZE, PROT_READ, MAP_FILE, fd, idx, last_mapped_offset);
}
*(addr + (idx % MAPSIZE));
idx += 30;
}
munmap(addr, MAPSIZE);
close(fd);
バッファ付き I/O ライブラリの全体的な目的は、このような懸念から解放することです。30 バイトごとに読み取る必要がある場合、OS はより大きなチャンクで読み取るため、OS はファイル全体を読み取ることになります。最高のパフォーマンスから最低のパフォーマンスまで、次のオプションがあります。
大きなアドレス空間がある場合 (つまり、64 ビット ハードウェアで 64 ビット OS を実行している場合)、メモリ マップド IO (
mmap
POSIX システム上) を使用すると、OS がカーネルからデータをコピーするコストを節約できます。空間からユーザー空間へ。この節約はかなりの量になる可能性があります。以下の詳細なメモに示されているように (ベンチマークの Steve Jessop に感謝します)、I/O パフォーマンスに関心がある場合は、AT&T Advanced Software Technology グループからPhong Vo のsfio ライブラリをダウンロードする必要があります。C の標準 I/O ライブラリよりも安全で、設計が優れており、高速です。
fseek
使用量の多いプログラムでは劇的に高速で、単純なマイクロベンチマークでは最大 7 倍高速です。問題を解決するために正確に設計および実装されている
fseek
とをリラックスして使用してください。fgetc
この問題を真剣に受け止める場合は、3 つの選択肢すべてを測定する必要があります。Steve Jessop と私は、usingfseek
の方が遅く、GNU C ライブラリを使用している場合はfseek
はるかに遅いことを示しました。測定する必要がありmmap
ます。最速かもしれません。
補遺:ファイルシステムを調べて、ディスクから 2 ~ 3 GB をすばやく引き出せることを確認したいと考えています。たとえば、XFS は ext2 に勝る可能性があります。もちろん、NTFS や HFS+ に固執している場合は、単に遅くなります。
衝撃的な結果
Linux で Steve Jessop の測定を繰り返しました。GNU C ライブラリは、毎回システム コールfseek
を行います。なんらかの理由で POSIX がこれを要求しない限り、それは正気ではありません。たくさんの 1 と 0 を噛み砕いて、それよりも優れたバッファー付き I/O ライブラリを吐くことができました。とにかく、コストは約 20 倍に増加し、その多くはカーネルで費やされます。fgetc
シングル バイトの読み取りの代わりに使用するとfread
、小さなベンチマークで約 20% 節約できます。
適切な I/O ライブラリを使用すると、衝撃的な結果が少なくなります
今回は Phong Vo のsfio
ライブラリを使用して、もう一度実験を行いました。200MBの読み込みにかかる
- 使用せずに 0.15 秒
fseek
(BUFSZ
は 30k) - 0.57秒使用
fseek
繰り返し測定すると、 を使用せずfseek
に sfio を使用しても実行時間が約 10% 短縮されますが、実行時間は非常に長くなります (ほとんどすべての時間が OS で費やされます)。
このマシン (ラップトップ) には、ディスク キャッシュに収まらないファイルを実行するための十分な空きディスク領域がありませんが、次の結論を引き出します。
適切なI/O ライブラリを使用すると、よりコストがかかりますが、大きな違いを生むほど
fseek
高価ではありません (I/O だけの場合は 4 秒)。GNU プロジェクトは実用的な I/O ライブラリを提供していません。よくあることですが、GNU ソフトウェアは最悪です。
結論:高速な I/O が必要な場合は、最初に GNU I/O ライブラリを AT&T sfio ライブラリに置き換える必要があります。他の影響は、比較すると小さい可能性があります。
ほとんど気にする必要はありません。ランタイムは、ファイル ハンドルごとに読み取った最後のブロックを十分にバッファリングする場合があります。そうでない場合でも、オペレーティング システムはファイル アクセスをキャッシュしています。
つまり、一度にブロックを読み取ると、fseek および fread 関数の呼び出しオーバーヘッドを節約できます。一度に読み取るブロックが大きいほど、呼び出しのオーバーヘッドを節約できますが、他のコストは明らかに特定のポイントを超えて感じ始めます.
回転するプラッタを使用してハードディスクからデータを読み取る場合、答えは、大きなバッファを使用してファイル全体を順番に読み取り、不要な部分をメモリに破棄することです。
標準のハードディスクドライブへのアクセスの最小単位はセクターです。すべての一般的な回転ディスクドライブのセクターサイズは、30バイトを超える場合があります。つまり、ハードディスクコントローラは、ホストからの要求がどのように見えるかに関係なく、とにかくすべてのセクターにアクセスする必要があります。これを変更するために可能な低レベルの魔法はありません。
これが当てはまらず、個々のバイトを読み取ることができたとしても、シークとシーケンシャル読み取りの操作には大きなプレミアムがあります。考えられる最良のケースは、シーケンシャル読み取りと同じです。現実の世界では、シグナリングのオーバーヘッドによって、そのようなスキームが大規模なコマンドバッファを使用しても機能しなくなる場合でも、私は驚かないでしょう。