私はプロジェクトのメモリマップトファイルを調査してきましたが、以前にそれらを使用したことがあるか、使用しないことに決めた人からの考えをいただければ幸いです。その理由は何ですか。
特に、重要度の高い順に、次のことが懸念されます。
- 並行性
- ランダムアクセス
- パフォーマンス
- 使いやすさ
- 移植性
私はプロジェクトのメモリマップトファイルを調査してきましたが、以前にそれらを使用したことがあるか、使用しないことに決めた人からの考えをいただければ幸いです。その理由は何ですか。
特に、重要度の高い順に、次のことが懸念されます。
回答:
利点は、ファイルを読み取る従来の方法よりも必要なデータコピーの量を減らすことができることだと思います。
アプリケーションがメモリマップファイルの「インプレース」でデータを使用できる場合、コピーせずにデータを取り込むことができます。システムコール(Linuxのpread()など)を使用する場合、通常、カーネルが独自のバッファーからユーザースペースにデータをコピーする必要があります。この余分なコピーは時間がかかるだけでなく、データのこの余分なコピーにアクセスすることによってCPUのキャッシュの有効性を低下させます。
データを実際にディスクから読み取る必要がある場合(物理I / Oの場合など)、OSはデータを読み込む必要があります。ページフォールトは、システムコールよりもパフォーマンス面で優れているとは言えませんが、そうしないでください(つまり、すでにOSキャッシュにあります)。理論的には、パフォーマンスははるかに優れているはずです。
欠点は、メモリマップファイルへの非同期インターフェイスがないことです。マップされていないページにアクセスしようとすると、ページフォールトが生成され、スレッドがI / Oを待機します。
メモリマップトファイルの明らかな欠点は、32ビットOS上にあることです。アドレス空間が簡単に不足する可能性があります。
ユーザーが入力している間、メモリマップファイルを使用して「オートコンプリート」機能を実装しました。1つのインデックスファイルに100万をはるかに超える製品部品番号が保存されています。ファイルにはいくつかの典型的なヘッダー情報がありますが、ファイルの大部分はキーフィールドでソートされた固定サイズのレコードの巨大な配列です。
実行時に、ファイルはメモリマップされ、C
スタイルのstruct
配列にキャストされます。バイナリ検索を実行して、ユーザーが入力したときに一致する部品番号を見つけます。ファイルのいくつかのメモリページのみが実際にディスクから読み取られます-バイナリ検索中にヒットしたページ。
メモリマップトファイルは、読み取り/書き込みアクセスを置き換えるため、または同時共有をサポートするために使用できます。それらを1つのメカニズムに使用すると、もう1つのメカニズムも取得します。
ファイルを探したり、書き込んだり、読み取ったりするのではなく、ファイルをメモリにマップして、期待する場所にアクセスするだけです。
これは非常に便利であり、仮想メモリインターフェイスによってはパフォーマンスを向上させることができます。オペレーティングシステムが他のすべてのプログラムメモリアクセスとともにこの以前の「ファイルI / O」を管理できるようになり、(理論的には)すでにサポートに使用されているページングアルゴリズムなどを活用できるため、パフォーマンスが向上する可能性があります。プログラムの残りの部分の仮想メモリ。ただし、基盤となる仮想メモリシステムの品質によって異なります。私が聞いた逸話では、Solarisおよび* BSD仮想メモリシステムはLinuxのVMシステムよりも優れたパフォーマンスの向上を示す可能性がありますが、これをバックアップするための経験的データはありません。YMMV。
マップされたメモリを介して同じ「ファイル」を使用する複数のプロセスの可能性を検討すると、並行性が浮き彫りになります。読み取り/書き込みモデルでは、2つのプロセスがファイルの同じ領域に書き込んだ場合、プロセスのデータの1つがファイルに到着し、他のプロセスのデータを上書きすることはほぼ確実です。あなたはどちらか一方を手に入れるでしょう-しかし、いくつかの奇妙な混ざり合いはありません。これが標準で義務付けられている動作であるかどうかはわかりませんが、かなり信頼できるものです。(実際には良いフォローアップの質問です!)
対照的に、マップされた世界では、2つのプロセスが両方とも「書き込み」であると想像してください。これを行うには、「メモリストア」を実行します。これにより、O / Sがデータをディスクにページアウトします(最終的には)。ただし、その間に、重複する書き込みが発生することが予想されます。
これが例です。2つのプロセスが両方ともオフセット1024で8バイトを書き込んでいるとします。プロセス1は「11111111」を書き込んでおり、プロセス2は「22222222」を書き込んでいます。ファイルI / Oを使用している場合、O / Sの奥深くに、1でいっぱいのバッファーと、2でいっぱいのバッファーがあり、どちらもディスク上の同じ場所に向かっていることが想像できます。それらの1つは最初にそこに到達し、もう1つは2番目に到達します。この場合、2番目のものが勝ちます。 ただし、メモリマップトファイルアプローチを使用している場合、プロセス1は4バイトのメモリストアに移動し、続いて4バイトの別のメモリストアに移動します(これが最大メモリストアサイズではないと仮定します)。プロセス2も同じことをします。プロセスがいつ実行されるかに基づいて、次のいずれかが表示されると予想できます。
11111111
22222222
11112222
22221111
これに対する解決策は、明示的な相互排除を使用することです。これは、いずれにしてもおそらく良い考えです。とにかく、ファイルの読み取り/書き込みI / Oの場合、「正しいこと」を行うためにO / Sに依存していました。
分類相互排除プリミティブはミューテックスです。メモリマップトファイルの場合、(たとえば)pthread_mutex_init()を使用して利用できるメモリマップトミューテックスを確認することをお勧めします。
1つの落とし穴で編集する:マップされたファイルを使用している場合、ファイル内のデータへのポインターをファイル自体に埋め込みたいという誘惑があります(マップされたファイルに格納されているリンクリストを考えてください)。ファイルは異なる絶対アドレスに異なる時間に、または異なるプロセスでマップされる可能性があるため、これは望ましくありません。代わりに、マップされたファイル内でオフセットを使用してください。