mmap()と読み取りブロック


185

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

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

'dブロックはページサイズの境界(私の理解)にある必要があり、レコードがページ境界を越えて潜在的に好きになるmmap()可能性があるため、コードは非常に複雑になるmmap可能性があります。ではfstream、私たちが読んブロックに限定していないことからS、私はページ上の嘘は、境界をサイズのことを、レコードの開始をシークして、もう一度読み始めることができます。

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


1
これは興味深い読みです:medium.com/@sasha_f/…実験でmmap()は、syscallsを使用するよりも2〜6倍高速ですread()
mplattner

回答:


208

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

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

しかしながら、

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

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

  • 一部のJavaプログラマーは、非ブロックI / OがブロックI / Oよりも遅いことが多いことにショックを受けました。これは、非ブロックI / Oがより多くのシステムコールを実行する必要があることを知っている場合に最適です。

  • 他の一部のネットワークプログラマーは、これepollよりも遅いことが多いことを知ってショックを受けましたpoll。これは、管理でepollより多くのシステムコールを行う必要があることを知っている場合に最適です。

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

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


2000年代のハードウェアとソフトウェアに基づくアドバイスを、今日テストせずに使用することは、非常に疑わしいアプローチであることを覚えておいてください。また、そのスレッドのmmapvs に関する多くの事実read()は以前と同様に真実ですが、全体的なパフォーマンスは、長所と短所を合計しても実際には決定できず、特定のハードウェア構成でテストすることによってのみ決定できます。たとえば、「mmapへの呼び出しは読み取りよりもオーバーヘッドが多い」ということは議論の余地があります-はいmmap、プロセスページテーブルにマッピングを追加する必要がreadありますが、カーネルからユーザースペースにすべての読み取りバイトをコピーする必要があります。
BeeOnRope 2018年

その結果、私の(最新のIntel、2018年頃)ハードウェアでmmapは、readページサイズより大きい(4 KiB)読み取りよりもオーバーヘッドが低くなります。さて、データにまばらかつランダムにアクセスしたいのであれば、それmmapは本当に良いことですが、その逆は必ずしも必要ではなくmmap、シーケンシャルアクセスにも最適かもしれません。
BeeOnRope 2018年

1
@BeeOnRope:2000年代のハードウェアとソフトウェアに基づくアドバイスには懐疑的かもしれませんが、方法論とデータを提供しないベンチマークにはさらに懐疑的です。mmapより高速なケースを作成したい場合は、表形式の結果とプロセッサのモデル番号を備えたテスト装置全体(ソースコード)を最小限に抑えることを期待しています。
ディートリッヒエップ2018年

@BeeOnRope:また、このようなメモリシステムのビットをテストする場合、TLBフラッシュがプログラムの他の部分のパフォーマンスに悪影響を与える可能性があるため、マイクロベンチマークは非常に誤解を招く可能性があることに注意してください。 mmap自体を測定するだけです。
ディートリッヒエップ2018年

2
@DietrichEpp-はい、TLB効果に精通します。mmap異常な状況を除いてTLBをフラッシュしないことに注意してください(ただし、munmap可能性があります)。私のテストには、マイクロベンチマーク(を含むmunmap、実際のユースケースで実行される「アプリケーション内」の両方が含まれていました。もちろん、私のアプリケーションはあなたのアプリケーションと同じではないので、人々はローカルでテストするべきです。mmapマイクロベンチマークのメリットは明らかではありません。read()ユーザー側の宛先バッファーは通常L1にとどまるため、大きなアプリケーションでは発生しない可能性があるため、大幅に向上します。ええ、「それは複雑です」。
BeeOnRope 2018年

47

主なパフォーマンスコストは、ディスクI / Oです。「mmap()」はistreamよりも確かに高速ですが、ディスクI / Oが実行時間を支配するため、違いは顕著ではない場合があります。

私は「のmmap()があることを彼の主張をテストする(上記/下記参照)ベン・コリンズのコードの断片を試してみましたより速く」および測定可能な差は認められませんでした。彼の答えについての私のコメントを見てください。

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

あなたのケースでは、mmap()、istream、低レベルのopen()/ read()呼び出しはすべてほぼ同じだと思います。これらの場合には、mmap()をお勧めします。

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

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


ランダムアクセスについての良い点:これは私の認識を駆り立てるものの1つかもしれません。
ベンコリンズ、

1
ファイルがメモリに快適に収まる必要があるとは言いませんが、アドレス空間にのみ収まります。したがって、64ビットシステムでは、巨大なファイルをマップしない理由はありません。OSはその処理方法を知っています。これはスワッピングに使用されるのと同じロジックですが、この場合、ディスクに追加のスワップ領域は必要ありません。
MvG、2014年

@MvG:ディスクI / Oの要点を理解していますか?ファイルがメモリではなくアドレス空間に収まり、ランダムアクセスがある場合は、すべてのレコードアクセスでディスクヘッドの移動とシーク、またはSSDページ操作が必要になる可能性があり、パフォーマンスに悪影響を及ぼします。
Tim Cooper

3
ディスクI / Oの側面は、アクセス方法から独立している必要があります。RAMよりも大きいファイルに本当にランダムにアクセスできる場合、mmapとseek + readの両方がディスクに強く拘束されます。それ以外の場合は、両方ともキャッシュの恩恵を受けます。メモリサイズと比較したファイルサイズは、どちらの方向にも強い議論とは言えません。一方、ファイルサイズとアドレススペースは、特に真にランダムアクセスの場合、非常に強力な議論です。
MvG 2014年

私の元の答えは、「すべてがメモリに快適に収まるか、またはファイル内に参照の局所性がある」ということでした。したがって、2番目のポイントは、あなたが言っていることを扱います。
Tim Cooper

43

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()で編集できる複数のファイルに分割しようとする場合があります(はるかに単純です)。

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

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

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

回答リストを整理するために編集: @jbl:

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

確かに、私はGit用のC ++ライブラリ(もしそうならlibgit ++)を書いていましたが、これと同様の問題に遭遇しました:大きな(非常に大きな)ファイルを開くことができる必要があり、完全な性能を発揮する必要はありません(それはと同じstd::fstreamです)。

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


3
RE:Windowsのmmapedファイルキャッシュ。正確に言うと、ファイルバッファリングが有効になっている場合、カーネルメモリは、読み取っているファイルを内部的にマップし、そのバッファに読み取って、プロセスにコピーします。これは、追加のコピー手順を除いて、自分でメモリマップしたかのようです。
Chris Smith、

6
受け入れられた答えに同意するのは嫌ですが、この答えは間違っていると思います。私はあなたの提案に従って64ビットLinuxマシンでコードを試しましたが、mmap()はSTL実装よりも高速ではありませんでした。また、理論的には、「mmap()」がこれ以上速く(または遅く)なることを期待しません。
Tim Cooper、

3
@Tim Cooper:このスレッド(markmail.org/message/…)に興味があるかもしれません。2つのことに注意してください:mmapはLinuxでは適切に最適化されていません。また、最良の結果を得るには、テストでmadviseを使用する必要もあります。
ベンコリンズ

9
親愛なるベン:私はそのリンクを読みました。Linuxで「mmap()」が高速でなく、WindowsでMapViewOfFile()が高速でない場合、「mmapの方がはるかに高速」であると主張できますか?また、理論的な理由から、mmap()はシーケンシャルリードの方が高速ではないと思います。反対の説明はありますか?
Tim Cooper、

11
ベン、mmap()一度に1ページずつファイルを作成する必要があるのはなぜですか。a size_tがファイルのサイズを保持するのに十分な容量を持っている場合(64ビットシステムでは非常に可能性が高い)、mmap()1回の呼び出しでファイル全体のみ。
Steve Emmerson、

39

ここにはすでに多くの顕著な点をカバーする良い答えがたくさんあるので、私は直接上で対処されなかったいくつかの問題を追加します。つまり、この回答は賛否両論の包括的なものではなく、ここにある他の回答の補遺と考えてください。

mmapは魔法のようです

ファイルがすでに完全にキャッシュされている場合撮影1をベースラインとして2をmmapほとんどのように思えるかもしれません魔法

  1. mmap ファイル全体を(潜在的に)マップするために1回のシステムコールのみが必要で、その後はシステムコールが不要になります。
  2. mmap カーネルからユーザースペースへのファイルデータのコピーは必要ありません。
  3. mmapコンパイラーの自動ベクトル化、SIMD組み込み関数、プリフェッチ、最適化されたインメモリ解析ルーチン、OpenMPなど、メモリに対して実行できる高度なトリックでの処理を含め、「メモリとして」ファイルにアクセスできます。

ファイルがすでにキャッシュにある場合、打ち負かすのは不可能のようです。カーネルページキャッシュにメモリとして直接アクセスするだけで、それより速くなることはありません。

まあ、それはできます。

mmapは実際には魔法ではありません...

mmapは引き続きページごとの作業を行います

mmapvsの主な隠れたコストread(2)(実際にはブロック読み取るための同等のOSレベルのsyscallです)はmmap、ユーザー空間で4Kページごとに「何らかの作業」を行う必要があることです。ページフォルトメカニズム。

例として、mmapファイル全体を格納する一般的な実装では、100 GB / 4K = 2500万のフォールトをフォールトインして、100 GBのファイルを読み取る必要があります。さて、これらはマイナーフォールトになりますが、250億ページフォールトはまだ超高速ではありません。マイナーフォールトのコストは、最良の場合、おそらく数百ナノ秒になります。

mmapはTLBパフォーマンスに大きく依存しています

これで、に渡しMAP_POPULATEmmap、戻る前にすべてのページテーブルをセットアップするように指示できるため、アクセス中にページフォールトが発生しないはずです。これには、ファイル全体をRAMに読み込むという小さな問題があります。100GBのファイルをマップしようとすると、問題が発生しますが、今は無視してください3。カーネルはこれらのページテーブルをセットアップするためにページごとの作業を行う必要があります(カーネル時間として表示されます)。これは、mmapアプローチの主要なコストとなり、ファイルサイズに比例します(つまり、ファイルサイズが大きくなってもそれほど重要ではなくなります)4

最後に、ユーザー空間にアクセスする場合でも、そのようなマッピングは完全にフリーではありません(ファイルベースのに由来しない大容量のメモリバッファーと比較してmmap)-ページテーブルがセットアップされても、新しいページへの各アクセスは、概念的には、TLBミスが発生します。mmapファイルを使用するということは、ページキャッシュとその4Kページを使用することを意味するので、100 GBのファイルでこのコストが2500万回発生します。

現在、これらのTLBミスの実際のコストは、ハードウェアの少なくとも以下の側面に大きく依存しています。(a)所有している4K TLBエンティティの数と、残りの変換キャッシュの動作(b)ハードウェアプリフェッチがどの程度適切に処理されるかTLBを使用-たとえば、プリフェッチでページウォークをトリガーできますか?(c)ページウォーキングハードウェアの速度と並列性。最新のハイエンドx86 Intelプロセッサでは、ページウォーキングハードウェアは一般に非常に強力です。少なくとも2つの並列ページウォーカーがあり、継続的な実行と同時にページウォークが発生し、ハードウェアプリフェッチがページウォークをトリガーする可能性があります。そのため、ストリーミング読み取り負荷に対するTLBの影響はかなり低く、そのような負荷はページサイズに関係なく同様に実行されることがよくあります。ただし、他のハードウェアは通常はるかに悪いです!

read()はこれらの落とし穴を回避します

read()一般根底に何であるシステムコール、タイプの呼び出しはC、C ++で、例えば提供され、他の言語は、誰もが、十分に認識していることを1主な欠点を持っている「ブロック読み」:

  • read()Nバイトを呼び出すたびに、カーネルからユーザー空間にNバイトをコピーする必要があります。

一方、上記のコストのほとんどを回避できます。2500万の4Kページをユーザー空間にマッピングする必要はありません。通常malloc、ユーザースペースで単一のバッファーと小さなバッファーを使用し、すべてのread呼び出しで繰り返し使用できます。カーネル側では、4KページやTLBミスの問題はほとんどありません。これは、通常、すべてのRAMがいくつかの非常に大きなページ(x86の1 GBページなど)を使用して線形にマップされるため、ページキャッシュ内の基になるページがカバーされるためです。カーネル空間で非常に効率的に。

したがって、基本的には、次の比較を行って、大きなファイルの1回の読み取りでどちらが速いかを判断します。

mmapアプローチによって暗示される追加のページごとの作業は、カーネルからユーザースペースにファイルコンテンツをコピーするバイトごとの作業よりもコストがかかりますread()か?

多くのシステムでは、実際にはほぼバランスが取れています。それぞれが、ハードウェアとOSスタックの完全に異なる属性でスケーリングされることに注意してください。

特に、このmmapアプローチは次の場合に比較的速くなります。

  • OSには、軽度の障害処理が高速であり、特に障害回避などの軽度の障害バルキングが最適化されています。
  • OSは、MAP_POPULATEたとえば、基礎となるページが物理メモリで隣接している場合に、大きなマップを効率的に処理できる優れた実装を備えています。
  • ハードウェアは、大きなTLB、高速の第2レベルのTLB、高速で並列のページウォーカー、翻訳との優れたプリフェッチ相互作用など、強力なページ変換パフォーマンスを備えています。

...次のread()場合、アプローチは比較的速くなります。

  • read()システムコールは、優れたコピー性能を有します。たとえば、copy_to_userカーネル側での良好なパフォーマンス。
  • カーネルは、メモリをマップする効率的な(ユーザーランドに比べて)方法を持っています。
  • カーネルには高速なsyscallがあり、syscall間でカーネルTLBエントリを維持する方法があります。

上記のハードウェア要素は、同じファミリー内(例:x86世代、特に市場セグメント内)でも、アーキテクチャ(例:ARM対x86対PPC)間でも、プラットフォームによって大きく異なります。

OSの要素も変化し続け、両側でさまざまな改善が行われたため、どちらか一方のアプローチの相対速度が大幅に向上しました。最近のリストには以下が含まれます:

  • 上記のフォールトアラウンドの追加。これは、がmmapない場合に非常に役立ちますMAP_POPULATE
  • 高速パスcopy_to_userメソッドの追加arch/x86/lib/copy_user_64.S(例:REP MOVQ高速のときに使用)read()。これは実際に役立ちます。

スペクターとメルトダウン後に更新

SpectreおよびMeltdownの脆弱性の緩和策により、システムコールのコストが大幅に増加しました。私が測定したシステムでは、「何もしない」システムコールのコスト(これは、システムコールの純粋なオーバーヘッドの見積もりであり、コールによって行われた実際の作業は別です)は、通常の約100 nsでした。約700 nsの最新のLinuxシステム。さらに、システムによっては、特にMeltdown のページテーブル分離の修正により、TLBエントリをリロードする必要があるため、直接のシステムコールコストとは別に、追加のダウンストリーム効果が生じる可能性があります。

メソッドは、「バッファサイズ」に相当するデータごとに1つのシステムコールを行う必要があるため、ベースメソッドread()と比較して、ベースメソッドの相対的な欠点です。大きなバッファーを使用すると通常L1サイズを超えてキャッシュミスが発生するため、パフォーマンスが低下するため、バッファーサイズを任意に増やしてこのコストを償却することはできません。mmapread()

一方、をmmap使用するMAP_POPULATEと、を使用してメモリの大きな領域にマップし、効率的にアクセスできますが、1回のシステムコールしかかかりません。


1これには、ファイルが最初に完全にキャッシュされなかったが、OSの先読みがそれを表示するのに十分な場合も含まれます(つまり、通常、ページは欲しい)。これは微妙な問題ですが、先読みの動作はmmapread呼び出しの間でかなり異なることが多く、2で説明されているように「アドバイス」呼び出しによってさらに調整できます。

2 ...ファイルがキャッシュされていない場合、アクセスパターンが基盤となるハードウェアにどれほど同情的であるかなど、IOの懸念によって動作が完全に支配されるため、そのようなアクセスができるだけ同情的であることを確認するための努力はすべて必要です。たとえば、madviseまたはfadvise呼び出し(およびアクセスパターンを改善するために行うことができるアプリケーションレベルの変更)を使用して可能です。

3たとえば、mmap小さいサイズ(たとえば100 MB)のウィンドウを順番に表示することで、これを回避できます。

4実際にMAP_POPULATEは、おそらくカーネルがフォールトアラウンドを使用しているため、アプローチが(少なくとも1つのハードウェア/ OSの組み合わせ)使用しない場合よりもわずかに速いことが判明しました -したがって、マイナーフォールトの実際の数は16分の1に減少しますとか、ぐらい。


4
この複雑な問題に対して、より微妙な答えを提供していただきありがとうございます。ほとんどの人にとって、mmapの方が速いことは明らかですが、実際にはそうではありません。私の実験では、数百万回のアクセスごとにバッファをmallocしていても、メモリ内インデックスを使用して100 GBの大規模なデータベースにランダムにアクセスする方がpread()で高速であることがわかりました。そして、業界の多くの人々が同じことを観察しているようです。
カエターノザウアー

5
ええ、それはシナリオに大きく依存します。読み取りが十分に小さく、時間の経過とともに同じバイトを繰り返し読み取る傾向がある場合mmap、固定カーネル呼び出しのオーバーヘッドが回避されるため、克服できない利点があります。一方、mmapTLBプレッシャーも増加し、現在のプロセスで最初にバイトが読み取られる「ウォームアップ」フェーズでは、実際には遅くなります(ただし、バイトはまだページページにあります)。readたとえば、隣接するページを「フォールトアラウンド」するよりも多くの作業...そして同じアプリケーションの場合は「ウォームアップ」が重要です!@CaetanoSauer
BeeOnRope

「... 250億のページフォールトはまだ超高速ではない...」と書いてあると思いますが、「...しかし2,500 万のページフォールトは超高速ではない...」 。私は100%ポジティブではないので、直接編集していません。
Ton van den Heuvel

7

ベン・コリンズがスライディングウィンドウのmmapソースコードを紛失したのは残念です。それはブーストで持っているといいでしょう。

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

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

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

これはすべて、WindowsでもCreateFileMapping()とMapViewOfFile()(およびSYSTEM_INFO.dwAllocationGranularity --- SYSTEM_INFO.dwPageSizeを取得するGetSystemInfo()を使用して)で正常に機能します。


私はググったところ、dwAllocationGranularityに関するこの小さなスニペットを見つけました-私はdwPageSizeを使用していて、すべてが壊れていました。ありがとう!
wickedchicken 2010年

4

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

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


3

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

また、次のレコードに進む前に、各レコードのすべての処理手順を実行できますか?多分それはIOオーバーヘッドのいくつかを避けるでしょうか?


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 file I / O still勝ちますが、これは物事をより近くするはずです。


2

私の考えでは、mmap()を使用することで、開発者は独自のキャッシュコードを記述する必要がなくなります。単純な「ファイルを1回だけ読み取る」ケースでは、これは難しいことではありません(ただし、mlbrockがメモリコピーをプロセススペースに保存していることを指摘しています)が、ファイル内を行き来する場合、またはビットをスキップするなど、カーネル開発者はおそらく私が実行できるよりも優れたキャッシングの実装を行っていると思います...


1
ほとんどの場合、ページサイズのチャンクを非常に盲目的に操作するカーネルが実行できるよりも、アプリケーション固有のデータをキャッシュする方が優れています(たとえば、単純な疑似LRUスキームのみを使用して、削除するページを決定します) )-適切なキャッシュの粒度について多くのことを知っていて、将来のアクセスパターンについてもよく理解しているかもしれません。mmapキャッシュの本当の利点は、すでに存在している既存のページキャッシュを再利用するだけなので、そのメモリを無料で利用でき、プロセス間で共有できることです。
BeeOnRope 2017年

2

何年も前に、ツリー構造を含む巨大なファイルをメモリにマッピングしたことを覚えています。ツリーノードの割り当てやポインタの設定など、メモリでの多くの作業を伴う通常の非シリアル化と比較して、速度に驚かされました。そのため、実際には、mmap(またはWindowsでの対応するもの)への1回の呼び出しを、オペレーターの新規呼び出しおよびコンストラクター呼び出しへの多くの(多くの)呼び出しと比較していました。このような種類のタスクでは、mmapは非シリアル化と比較して無敵です。もちろん、これについては、再配置可能なポインタを調べる必要があります。


それは災害のレシピのように聞こえます。オブジェクトのレイアウトが変更された場合はどうしますか?仮想関数がある場合、すべてのvftblポインターはおそらく間違っているでしょう。ファイルのマッピング先をどのように制御しますか?あなたはそれにアドレスを与えることができますが、それは単なるヒントであり、カーネルは別のベースアドレスを選択するかもしれません。
イェンス

これは、安定して明確に定義されたツリーレイアウトがある場合に完全に機能します。次に、すべてを関連する構造体にキャストし、毎回「mmap開始アドレス」のオフセットを追加して、内部ファイルポインターを追跡できます。これは、iノードとディレクトリツリーを使用して、ファイル・システムに非常によく似ている
Mike76

1

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


うん。私はそれについて考えていて、おそらく今後のリリースで試してみるでしょう。私が持っている唯一の予約は、処理がI / Oレイテンシよりもはるかに短いため、あまりメリットがない可能性があるということです。
jbl 2008

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なしでは何もしないとマニュアルで述べている場合でも、このフラグを使用するとより良い結果が得られます。


2
怠惰なヒントposix_madviseWILLNEEDフラグを事前に設定しておく必要があります。
ShadowRanger 2016年

@ShadowRanger、合理的に聞こえます。posix_madviseただし、非同期呼び出しであることを明確に示すために、manページを更新します。また、mlockページフォルトなしでメモリ領域全体が使用可能になるまで待機したい人のために参照するとよいでしょう。
2016年
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.