web-dev-qa-db-ja.com

C ++での高性能シーケンシャルファイルI / Oの最速の方法は何ですか?

次のことを前提としています...
出力:
ファイルが開かれます...
データはディスクに「ストリーミング」されます。メモリ内のデータは、連続した大きなバッファにあります。バッファから直接、そのままの形式でディスクに書き込まれます。バッファのサイズは設定可能ですが、ストリームの期間中は固定されています。バッファは次々とファイルに書き込まれます。シーク操作は行われません。
...ファイルが閉じています。

入力:
大きなファイル(上記のように順次書き込まれます)が最初から最後までディスクから読み取られます。


C++で可能な最速のシーケンシャルファイルI/Oを実現するための一般に受け入れられているガイドラインはありますか?

考えられるいくつかの考慮事項:

  • 最適なバッファーサイズを選択するためのガイドライン
  • Boost :: asioのようなポータブルライブラリは抽象化されすぎて特定のプラットフォームの複雑さを公開できないのでしょうか、それとも最適であると想定できますか?
  • 非同期I/Oは常に同期よりも望ましいですか?アプリケーションが他の方法でCPUにバインドされていない場合はどうなりますか?

これにはプラットフォーム固有の考慮事項があることを理解しています。一般的なガイドラインと特定のプラットフォームのガイドラインを歓迎します。
(Win x64に最も興味がありますが、SolarisとLinuxに関するコメントにも興味があります)

48
Adam Holmberg

C++で可能な最速のシーケンシャルファイルI/Oを実現するための一般に受け入れられているガイドラインはありますか?

ルール0:測定。利用可能なすべてのプロファイリングツールを使用して、それらについて理解します。それを測定しなかった場合、それがどれほど速いかわからないことは、プログラミングにおけるほぼ戒めであり、I/Oの場合、これはさらに真実です。可能であれば実際の作業条件でテストしてください。 I/Oシステムとの競合がないプロセスは、実際の負荷では存在しない条件に合わせて、過剰に最適化、微調整することができます。

  1. ファイルに書き込む代わりに、マップされたメモリを使用します。これは常に高速であるとは限りませんが、不必要なコピーを回避し、ディスクが実際に使用されている方法に関するOSの知識を利用することにより、オペレーティングシステム固有でありながら比較的ポータブルな方法でI/Oを最適化する機会を与えます。 (OS固有のAPI呼び出しではなく、ラッパーを使用する場合は「ポータブル」)。

  2. 出力を可能な限り線形化してみてください。キャッシュライン、ページング、およびその他のメモリサブシステムの問題が問題になり始めるため、書き込み用のバッファを見つけるためにメモリ内をジャンプする必要があると、最適化された条件下で顕著な影響が生じる可能性があります。バッファがたくさんある場合は、線形化を行うscatter-gather I/Oのサポートを調べてください。

考えられるいくつかの考慮事項:

  • 最適なバッファーサイズを選択するためのガイドライン

初心者向けのページサイズですが、そこから調整する準備をしてください。

  • Boost :: asioのようなポータブルライブラリは抽象化されすぎて特定のプラットフォームの複雑さを公開できないのでしょうか、それとも最適であると想定できますか?

それが最適であると思い込まないでください。それは、ライブラリがプラットフォームでどれほど徹底的に実行され、開発者がライブラリを高速化するためにどれだけの努力を注いだかに依存します。ポータブルI/Oライブラリcanは、ほとんどのシステムに高速な抽象化が存在するため、非常に高速であると述べましたが、通常、多くのベースをカバーする一般的なAPIを思い付くことが可能です。 Boost.Asioは、私の知る限りでは、それが搭載されている特定のプラットフォーム用にかなり細かく調整されています。高速非同期I/O用のOSおよびOSバリアント固有のAPIのファミリ全体があります(例 epoll/ dev/epollkqueueWindowsオーバーラップI/O )、およびAsioはそれらをすべてラップします。

  • 非同期I/Oは常に同期よりも望ましいですか?アプリケーションが他の方法でCPUにバインドされていない場合はどうなりますか?

非同期I/Oは、同期I/Oよりも生の意味で高速ではありません。非同期I/Oが行うことは、yourコードがI/Oの完了を待機する時間を無駄にしないようにすることです。 I/Oの準備ができたときではなく前にコードにコールバックするため、その時間を無駄にしない他の方法、つまりスレッドを使用するよりも一般的な方法で高速です。アイドルスレッドを終了する必要があるという誤った開始や懸念はありません。

31
quark

一般的なアドバイスは、大きなチャンクでのバッファリングと読み取り/書き込みをオフにすることです(ただし、大きすぎない場合は、I/O全体が完了するのを待機するのに時間がかかりすぎます。そうしないと、最初のメガバイトからすでに大量のデータを取り出し始める可能性があります。このアルゴリズムでスイートスポットを見つけるのは簡単です。回すノブは1つだけです(チャンクサイズ)。

それ以上に、入力mmap() ingの場合、ファイルを共有して読み取り専用にするのが(最速でない場合は)最も効率的な方法です。使用しているプラ​​ットフォームにmadvise()を呼び出して、ファイルをどのように走査するかをカーネルに指示します。これにより、先読みを行い、後でページをすばやくすばやく破棄できます。

出力の場合、すでにバッファがある場合は、ファイルで(またmmap()で)支えることを検討してください。ユーザー空間にデータをコピーする必要はありません。

mmap()が好みに合わない場合は、fadvise()があり、非常に難しいものには非同期ファイルI/Oがあります。

(上記はすべてPOSIXであり、Windowsの名前は異なる場合があります)。

12

Windowsの場合、プラットフォーム固有のWindows API呼び出しを使用することを選択した場合は、CreateFile()呼び出しでFILE_FLAG_SEQUENTIAL_SCANを必ず使用する必要があります。これにより、I/Oのキャッシュが最適化されます。バッファーサイズに関する限り、通常はディスクセクターサイズの倍数のバッファーサイズをお勧めします。 8Kは、大きくなることからほとんど得られない、素晴らしい出発点です。

この記事では、Windowsでの非同期と同期の比較について説明します。

http://msdn.Microsoft.com/en-us/library/aa365683(VS.85).aspx

上記のように、すべては使用しているマシン/システム/ライブラリに依存します。あるシステムでの高速なソリューションは、別のシステムでは遅い場合があります。

ただし、一般的なガイドラインは、可能な限り大きなチャンクで書き込むことです。
通常、一度に1バイトずつ書き込むのが最も低速です。

確実に知る最良の方法は、いくつかの異なる方法をコーディングしてプロファイルすることです。

3
KPexEA

Linuxでは、バッファー付き読み取りと書き込みにより、バッファーサイズが大きくなるにつれて、処理速度が大幅に向上しますが、戻り値は減少しており、BUFSIZstdio.hで定義)をより大きなバッファーサイズとして使用することをお勧めします。あまり役に立ちません。

mmapingはファイルへの最速のアクセスを提供しますが、mmap呼び出し自体はかなり高価です。小さなファイル(16KiB)の場合、readおよびwriteシステムコールはwin( https://stackoverflow.com/a/39196499/1084774 を参照) readおよびmmap)。

2
PSkocik

あなたはC++について尋ねましたが、それはあなたはそれを過ぎて、プラットフォーム固有の小さなものを手に入れる準備ができているようです。

Windowsでは、FILE_FLAG_SEQUENTIAL_SCANファイルマッピングを使用すると、おそらく最速の方法です。実際、ファイルが実際にディスクに書き込まれる前に、プロセスが終了する可能性があります。明示的にブロックするフラッシュ操作がない場合、Windowsがこれらのページの書き込みを開始するまでに最大5分かかることがあります。

ファイルがローカルデバイスではなくネットワークドライブにある場合は注意が必要です。ネットワークエラーはSEHエラーとして表示されるので、処理する準備をする必要があります。

* nixesでは、rawディスクデバイスに順次書き込みを行うと、パフォーマンスが少し向上する可能性があります。これはWindowsでも可能ですが、APIではサポートされていません。これにより、ファイルシステムのオーバーヘッドが少しは回避されますが、役立つほど十分ではない場合があります。

大まかに言えば、RAMはディスクよりも1000倍以上高速で、CPUも高速です。ディスクヘッドの移動を回避することを除いて、役立つ論理的な最適化はあまりありません(シーク)可能な場合はいつでも、このファイル専用のディスクは、ここで非常に役立ちます。

2
Marsh Ray

CreateFileReadFileを使用すると、絶対的に高速なパフォーマンスが得られます。 FILE_FLAG_SEQUENTIAL_SCANでファイルを開きます。

2のべき乗のバッファーサイズで読み取ります。ベンチマークのみがこの数を決定できます。一度は8Kになるのを見てきました。もう一度、8Mであることがわかりました。これは大きく異なります。

これは、CPUキャッシュのサイズ、OSの先読みの効率、および多数の小さな書き込みの実行に伴うオーバーヘッドに依存します。

メモリマッピングはnotが最速の方法です。ブロックサイズを制御できず、OSがすべてのページで障害を起こす必要があるため、オーバーヘッドが大きくなります。

2
usr