ディスク上のデータベース ファイルにアクセスするために、通常の read() および write() 呼び出しの代わりに mmap() を使用するように、組み込みデータベース システムである SQLite を変更する実験を行っています。ファイル全体に 1 つの大きなマッピングを使用する。ファイルが十分に小さいため、仮想メモリ内でこのファイル用のスペースを見つけるのに問題はないと仮定します。
ここまでは順調ですね。多くの場合、mmap() を使用した方が read() や write() よりも少し速いようです。また、場合によってははるかに高速です。
データベース ファイルを拡張する書き込みトランザクションをコミットするためにマッピングのサイズを変更すると、問題が発生するようです。データベース ファイルを拡張するために、コードは次のようになります。
ftruncate(); // extend the database file on disk
munmap(); // unmap the current mapping (it's now too small)
mmap(); // create a new, larger, mapping
次に、新しいデータを新しいメモリ マッピングの最後にコピーします。ただし、データベース ファイルの各ページに次にアクセスするときにマイナー ページ フォールトが発生し、システムが仮想メモリ アドレスに関連付ける正しいフレームを OS ページ キャッシュで検索する必要があるため、munmap/mmap は望ましくありません。つまり、後続のデータベース読み取りが遅くなります。
Linux では、munmap()/mmap() の代わりに非標準の mremap() システム コールを使用して、マッピングのサイズを変更できます。これにより、マイナーなページ フォールトが回避されるようです。
質問: mremap() を持たない OSX などの他のシステムでは、これをどのように処理する必要がありますか?
現時点で2つのアイデアがあります。そして、それぞれに関する質問:
1) データベース ファイルよりも大きなマッピングを作成します。次に、データベース ファイルを拡張するときに、単に ftruncate() を呼び出してディスク上のファイルを拡張し、同じマッピングを使用し続けます。
これは理想的であり、実際に機能するようです。ただし、man ページの次の警告が心配です。
「ファイルの追加または削除された領域に対応するページ上のマッピングの基になるファイルのサイズを変更した場合の影響は不明です。」
質問: これは私たちが心配すべきことですか? それとも現時点での時代錯誤?
2) データベース ファイルを拡張する場合、mmap() の最初の引数を使用して、仮想メモリ内の現在のマッピングの直後にあるデータベース ファイルの新しいページに対応するマッピングを要求します。初期マッピングを効果的に拡張します。最初のマッピングの直後に新しいマッピングを配置する要求をシステムが受け入れることができない場合は、munmap/mmap にフォールバックします。
実際には、OSX はこの方法でマッピングを配置するのに非常に優れていることがわかっているため、このトリックはそこで機能します。
質問: システムが仮想メモリ内の最初のマッピングの直後に 2 番目のマッピングを割り当てた場合、最終的に munmap() への 1 回の大きな呼び出しを使用して両方のマッピングを解除しても安全ですか?