ここにはすでに多くの顕著な点をカバーする良い答えがたくさんあるので、私は直接上で対処されなかったいくつかの問題を追加します。つまり、この回答は賛否両論の包括的なものではなく、ここにある他の回答の補遺と考えてください。
mmapは魔法のようです
ファイルがすでに完全にキャッシュされている場合撮影1をベースラインとして2を、mmap
ほとんどのように思えるかもしれません魔法:
mmap
ファイル全体を(潜在的に)マップするために1回のシステムコールのみが必要で、その後はシステムコールが不要になります。
mmap
カーネルからユーザースペースへのファイルデータのコピーは必要ありません。
mmap
コンパイラーの自動ベクトル化、SIMD組み込み関数、プリフェッチ、最適化されたインメモリ解析ルーチン、OpenMPなど、メモリに対して実行できる高度なトリックでの処理を含め、「メモリとして」ファイルにアクセスできます。
ファイルがすでにキャッシュにある場合、打ち負かすのは不可能のようです。カーネルページキャッシュにメモリとして直接アクセスするだけで、それより速くなることはありません。
まあ、それはできます。
mmapは実際には魔法ではありません...
mmapは引き続きページごとの作業を行います
mmap
vsの主な隠れたコストread(2)
(実際にはブロックを読み取るための同等のOSレベルのsyscallです)はmmap
、ユーザー空間で4Kページごとに「何らかの作業」を行う必要があることです。ページフォルトメカニズム。
例として、mmap
ファイル全体を格納する一般的な実装では、100 GB / 4K = 2500万のフォールトをフォールトインして、100 GBのファイルを読み取る必要があります。さて、これらはマイナーフォールトになりますが、250億ページフォールトはまだ超高速ではありません。マイナーフォールトのコストは、最良の場合、おそらく数百ナノ秒になります。
mmapはTLBパフォーマンスに大きく依存しています
これで、に渡しMAP_POPULATE
てmmap
、戻る前にすべてのページテーブルをセットアップするように指示できるため、アクセス中にページフォールトが発生しないはずです。これには、ファイル全体を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サイズを超えてキャッシュミスが発生するため、パフォーマンスが低下するため、バッファーサイズを任意に増やしてこのコストを償却することはできません。mmap
read()
一方、をmmap
使用するMAP_POPULATE
と、を使用してメモリの大きな領域にマップし、効率的にアクセスできますが、1回のシステムコールしかかかりません。
1これには、ファイルが最初に完全にキャッシュされなかったが、OSの先読みがそれを表示するのに十分な場合も含まれます(つまり、通常、ページは欲しい)。これは微妙な問題ですが、先読みの動作はmmap
とread
呼び出しの間でかなり異なることが多く、2で説明されているように「アドバイス」呼び出しによってさらに調整できます。
2 ...ファイルがキャッシュされていない場合、アクセスパターンが基盤となるハードウェアにどれほど同情的であるかなど、IOの懸念によって動作が完全に支配されるため、そのようなアクセスができるだけ同情的であることを確認するための努力はすべて必要です。たとえば、madvise
またはfadvise
呼び出し(およびアクセスパターンを改善するために行うことができるアプリケーションレベルの変更)を使用して可能です。
3たとえば、mmap
小さいサイズ(たとえば100 MB)のウィンドウを順番に表示することで、これを回避できます。
4実際にMAP_POPULATE
は、おそらくカーネルがフォールトアラウンドを使用しているため、アプローチが(少なくとも1つのハードウェア/ OSの組み合わせ)使用しない場合よりもわずかに速いことが判明しました -したがって、マイナーフォールトの実際の数は16分の1に減少しますとか、ぐらい。
mmap()
は、syscallsを使用するよりも2〜6倍高速ですread()
。