221

サイズが100GB以上になる可能性のあるファイルを処理するプログラムに取り組んでいます。ファイルには、可変長レコードのセットが含まれています。私は最初の実装を立ち上げて実行しており、現在、特に入力ファイルが何度もスキャンされるため、I/Oをより効率的に実行することでパフォーマンスの向上を目指しています。

mmap()C ++のライブラリを介してブロックを使用するか読み取るかについての経験則はありfstreamますか?私がやりたいのは、ディスクからバッファーに大きなブロックを読み取り、バッファーから完全なレコードを処理してから、さらに読み取ることです。

'dブロックはページサイズの境界上にある必要があり(私の理解)、レコードはページ境界を越えて存在する可能性があるため、mmap()コードは非常に乱雑になる可能性があります。mmapsを使用fstreamすると、ページサイズの境界にあるブロックの読み取りに制限されないため、レコードの先頭を探して再度読み取りを開始できます。

最初に完全な実装を実際に作成せずに、これら2つのオプションのどちらかを決定するにはどうすればよいですか?経験則(たとえば、mmap()2倍速い)または簡単なテストはありますか?

4

12 に答える 12

244

Linux での mmap / read パフォーマンスに関する最後の言葉を見つけようとしていたところ、Linux カーネルのメーリング リストで素敵な投稿 (リンク) を見つけました。これは 2000 年からのもので、それ以来、カーネルの IO と仮想メモリに多くの改善が行われてきましたが、その理由がうまく説明されていmmapますread

  • への呼び出しにmmapは、 よりも多くのオーバーヘッドがありますread( よりも多くのオーバーヘッドがあるようepollに、 よりもpoll多くのオーバーヘッドがありますread)。仮想メモリ マッピングの変更は、異なるプロセス間の切り替えにコストがかかるのと同じ理由で、一部のプロセッサでは非常にコストのかかる操作です。
  • IO システムはすでにディスク キャッシュを使用できるため、ファイルを読み取ると、どのような方法を使用してもキャッシュにヒットするか、キャッシュにアクセスできなくなります。

でも、

  • 特にアクセスパターンがまばらで予測不可能な場合は特に、メモリマップはランダムアクセスの方が一般的に高速です。
  • メモリ マップを使用すると、使い終わるまでキャッシュからページを使用し続けることができます。これは、ファイルを長時間頻繁に使用した場合、そのファイルを閉じてから再度開いた場合でも、ページはキャッシュされたままになることを意味します。では、ファイルが何read年も前にキャッシュからフラッシュされた可能性があります。ファイルを使用してすぐに破棄する場合は、この限りではありません。(mlockページをキャッシュに保持するためだけにページを作成しようとすると、ディスク キャッシュの裏をかこうとすることになり、この種の愚かさがシステム パフォーマンスに役立つことはめったにありません)。
  • ファイルを直接読み取るのは非常に簡単で高速です。

mmap/read の議論は、他の 2 つのパフォーマンスに関する議論を思い出させます。

  • 一部の Java プログラマーは、ノンブロッキング I/O がブロッキング I/O よりも遅いことが多いことを発見してショックを受けました。

  • 他のネットワーク プログラマーの中には、epollが よりも遅いことが多いことを知ってショックを受けた人もpollいますepoll

結論:データにランダムにアクセスする場合、データを長期間保持する場合、または他のプロセスと共有できることがわかっている場合(MAP_SHARED実際に共有しない場合はあまり面白くありません)、メモリマップを使用してください。データをシーケンシャルにアクセスするか、読み取り後に破棄する場合は、ファイルを正常に読み取ります。どちらかの方法でプログラムの複雑さが軽減される場合は、それを実行してください。多くの実際のケースでは、ベンチマークではなく実際のアプリケーションをテストせずに、より高速であることを示す確実な方法はありません。

(この質問を否定して申し訳ありませんが、私は答えを探していましたが、この質問は Google の検索結果の一番上に表示され続けました。)

于 2011-06-17T08:33:01.077 に答える
48

主なパフォーマンス コストは、ディスク I/O になります。「mmap()」は確かに istream よりも高速ですが、ディスク I/O がランタイムを支配するため、違いは目立たないかもしれません。

Ben Collins のコード フラグメント (上記/下記を参照) を試して、「mmap()のがはるかに高速である」という彼の主張をテストしましたが、測定可能な違いは見つかりませんでした。彼の答えに対する私のコメントを参照してください。

「レコード」が巨大でない限り、各レコードを順番に個別にmmapすることはお勧めしませ。これは非常に遅く、レコードごとに2つのシステムコールが必要であり、ディスクメモリキャッシュからページを失う可能性があります.... .

あなたの場合、mmap()、istream、および低レベルの open()/read() 呼び出しはすべてほぼ同じになると思います。次の場合は mmap() をお勧めします。

  1. ファイル内にランダム アクセス (シーケンシャルではない) があり、かつ
  2. 全体がメモリに快適に収まるか、ファイル内に参照の局所性があるため、特定のページをマップインし、他のページをマップアウトできます。こうすることで、オペレーティング システムは利用可能な RAM を最大限に活用できます。
  3. または、複数のプロセスが同じファイルを読み取り/処理している場合、すべてのプロセスが同じ物理ページを共有するため、mmap() は素晴らしいです。

(ところで - 私は mmap()/MapViewOfFile() が大好きです)。

于 2008-09-30T05:03:18.190 に答える
42

mmapのがはるかに高速です。あなたはそれをあなた自身に証明するために簡単なベンチマークを書くかもしれません:

char data[0x1000];
std::ifstream in("file.bin");

while (in)
{
  in.read(data, 0x1000);
  // do something with data
}

対:

const int file_size=something;
const int page_size=0x1000;
int off=0;
void *data;

int fd = open("filename.bin", O_RDONLY);

while (off < file_size)
{
  data = mmap(NULL, page_size, PROT_READ, 0, fd, off);
  // do stuff with data
  munmap(data, page_size);
  off += page_size;
}

明らかに、詳細(たとえば、ファイルが倍数でない場合にファイルの終わりに到達したときを判断する方法など)は省略していますpage_sizeが、実際にはこれよりもはるかに複雑であってはなりません。 。

可能であれば、データを複数のファイルに分割して、一部ではなく全体をmmap()することができます(はるかに簡単です)。

数か月前、boost_iostreams用のスライディングウィンドウmmap()-edストリームクラスの実装が中途半端でしたが、誰も気にせず、他のもので忙しくなりました。最も残念なことに、私は数週間前に古い未完成のプロジェクトのアーカイブを削除しました、そしてそれは犠牲者の1人でした:-(

更新:Microsoftは、最初にmmapで行うことのほとんどを実行する気の利いたファイルキャッシュを実装したため、このベンチマークはWindowsではまったく異なるように見えるという警告も追加する必要があります。つまり、頻繁にアクセスされるファイルの場合は、std :: ifstream.read()を実行するだけで、ファイルキャッシュがすでにメモリマッピングを実行していて、透過的であるため、mmapと同じくらい高速になります。

最終更新mmap:見てください、人々:OSと標準ライブラリ、ディスクとメモリ階層の多くの異なるプラットフォームの組み合わせにわたって、ブラックボックスとして表示されるシステムコールが常に常に大幅に高速になるとは言えませんよりread。たとえ私の言葉がそのように解釈されたとしても、それは私の意図ではありませんでした。 最終的に、私のポイントは、メモリマップドI/Oは一般的にバイトベースのI/Oよりも高速であるということでした。これはまだ真実です。実験的に2つの間に違いがないことがわかった場合、私にとって合理的と思われる唯一の説明は、プラットフォームが、呼び出しのパフォーマンスに有利な方法でメモリマッピングを内部で実装していることです。read。ポータブルな方法でメモリマップドI/Oを使用していることを完全に確認する唯一の方法は、を使用することmmapです。移植性を気にせず、ターゲットプラットフォームの特定の特性に依存できる場合は、readパフォーマンスをある程度犠牲にすることなく、を使用するのが適切な場合があります。

編集して回答リストをクリーンアップします: @jbl:

スライディングウィンドウのmmapは面白そうですね。それについてもう少し言えますか?

確かに-私はGit用のC++ライブラリ(必要に応じてlibgit ++)を作成していましたが、これと同様の問題が発生しました。大きな(非常に大きな)ファイルを開くことができ、パフォーマンスが完全に向上しないようにする必要がありました。 (と同じようにstd::fstream)。

Boost::Iostreamsすでにmapped_fileSourceがありますが、問題はmmapファイル全体にpingを実行していたため、2 ^(wordsize)に制限されていました。32ビットマシンでは、4GBでは十分ではありません。Gitにそれよりもはるかに大きくなるファイルがあることを期待するのは不合理ではない.packので、通常のファイルI/Oに頼らずにファイルをチャンクで読み取る必要がありました。のカバーの下で、とBoost::Iostreamsの間の相互作用の多かれ少なかれ別のビューであるソースを実装しました。に継承するだけで、同様のアプローチを試すこともできます。同様に、に継承します。正しく理解するのが難しいのは、2つの間の相互作用です。 std::streambufstd::istreamstd::filebufmapped_filebufstd::fstreama mapped_fstreamBoost::Iostreamsいくつかの作業が行われ、フィルターとチェーンのフックも提供されるので、そのように実装する方が便利だと思いました。

于 2008-09-05T15:12:02.247 に答える
7

申し訳ありませんが、Ben Collins はスライディング ウィンドウの mmap ソース コードを紛失してしまいました。ブーストに入れておけばよかった。

はい、ファイルのマッピングははるかに高速です。基本的に、OS の仮想メモリ サブシステムを使用して、メモリとディスクを関連付けたり、その逆を行ったりしています。このように考えてみてください。OS カーネル開発者がそれを高速化できれば、そうするでしょう。そうすることで、データベース、起動時間、プログラムのロード時間など、ほぼすべてが高速化されるためです。

複数の連続するページを一度にマップできるため、スライディング ウィンドウ アプローチはそれほど難しくありません。したがって、単一のレコードの最大サイズがメモリに収まる限り、レコードのサイズは重要ではありません。大切なのは帳簿のつけ方です。

レコードが getpagesize() 境界で始まらない場合、マッピングは前のページから開始する必要があります。マップされる領域の長さは、レコードの最初のバイト (必要に応じて getpagesize() の最も近い倍数に切り捨てられる) からレコードの最後のバイト (getpagesize() の最も近い倍数に切り上げられる) までです。レコードの処理が終了したら、それを unmap() して、次のレコードに進むことができます。

これは、CreateFileMapping() と MapViewOfFile() (および GetSystemInfo() を使用して SYSTEM_INFO.dwAllocationGranularity --- SYSTEM_INFO.dwPageSize ではない) を使用して、Windows でも問題なく機能します。

于 2008-09-15T23:09:57.787 に答える
5

mmapの方が速いはずですが、どれくらいかわかりません。それはあなたのコードに大きく依存します。mmapを使用する場合は、ファイル全体を一度にmmapするのが最善です。そうすれば、作業がずっと楽になります。潜在的な問題の1つは、ファイルが4GBより大きい場合(または実際には制限が低く、多くの場合2GB)、64ビットアーキテクチャが必要になることです。したがって、32環境を使用している場合は、おそらくそれを使用したくないでしょう。

そうは言っても、パフォーマンスを改善するためのより良いルートがあるかもしれません。入力ファイルは何度もスキャンされるとおっしゃいましたが、1回のパスで読み取り、それを実行できれば、はるかに高速になる可能性があります。

于 2008-09-05T15:11:22.450 に答える
3

おそらく、ファイルを前処理して、各レコードが別々のファイルに含まれるようにする必要があります(または、少なくとも各ファイルがmmap可能なサイズである必要があります)。

また、次のレコードに移動する前に、各レコードのすべての処理ステップを実行できますか?多分それはIOオーバーヘッドのいくらかを避けるでしょう?

于 2008-09-05T15:37:26.443 に答える
3

mmap 化されたファイル I/O が高速になることに同意しますが、コードのベンチマークを行っている間、カウンターの例を多少最適化するべきではありませんか?

ベン・コリンズは次のように書いています。

char data[0x1000];
std::ifstream in("file.bin");

while (in)
{
    in.read(data, 0x1000);
    // do something with data 
}

私も試してみることをお勧めします:

char data[0x1000];
std::ifstream iifle( "file.bin");
std::istream  in( ifile.rdbuf() );

while( in )
{
    in.read( data, 0x1000);
    // do something with data
}

さらに、0x1000がマシン上の仮想メモリの1ページのサイズではない場合に備えて、バッファサイズを仮想メモリの1ページと同じサイズにすることもできます... IMHO mmap'dファイルI / Oはまだ勝ちますが、これは物事を近づけるはずです。

于 2008-09-30T05:31:49.087 に答える
2

これは、マルチスレッドの良いユースケースのように思えます...他のスレッドがデータを処理している間にデータを読み取るように1つのスレッドを非常に簡単に設定できると思います。これは、知覚されるパフォーマンスを劇的に向上させる方法かもしれません。ちょっとした考え。

于 2008-09-06T13:41:00.240 に答える
1

mmapの最大の利点は、次のような非同期読み取りの可能性だと思います。

    addr1 = NULL;
    while( size_left > 0 ) {
        r = min(MMAP_SIZE, size_left);
        addr2 = mmap(NULL, r,
            PROT_READ, MAP_FLAGS,
            0, pos);
        if (addr1 != NULL)
        {
            /* process mmap from prev cycle */
            feed_data(ctx, addr1, MMAP_SIZE);
            munmap(addr1, MMAP_SIZE);
        }
        addr1 = addr2;
        size_left -= r;
        pos += r;
    }
    feed_data(ctx, addr1, r);
    munmap(addr1, r);

問題は、このメモリをファイルからできるだけ早く同期する必要があることを示唆する適切なMAP_FLAGSが見つからないことです。MAP_POPULATEがmmapの正しいヒントを提供することを願っています(つまり、呼び出しから戻る前にすべてのコンテンツをロードしようとはしませんが、feed_dataと非同期でロードします)。少なくとも、2.6.23以降、MAP_PRIVATEがないと何も実行されないとマニュアルに記載されている場合でも、このフラグを使用するとより良い結果が得られます。

于 2009-12-13T16:59:37.057 に答える