30

ディスク上のデータベース ファイルにアクセスするために、通常の 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 回の大きな呼び出しを使用して両方のマッピングを解除しても安全ですか?

4

3 に答える 3

6

2 は機能しますが、OS がたまたまスペースを利用できることに依存する必要はありません。事前にアドレス空間を予約して、固定された mmaping が常に成功するようにすることができます。

たとえば、1 ギガバイトのアドレス空間を予約するには。する

mmap(NULL, 1U << 30, PROT_NONE, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);

これにより、実際にメモリやリソースを割り当てることなく、1 ギガバイトの連続アドレス空間が予約されます。その後、このスペースで将来の mmaping を実行すると、成功します。そのため、返されたスペースの先頭にファイルを mmap し、必要に応じて fixed フラグを使用してファイルのさらなるセクションを mmap します。アドレス空間はすでに割り当てられ、予約されているため、mmaps は成功します。

注: Linux には MAP_NORESERVE フラグもあります。これは、RAM を割り当てている場合に初期マッピングに必要な動作ですが、私のテストでは、PROT_NONE はまだリソースを割り当てたくないことを示すのに十分であるため、無視されます。

于 2018-07-18T01:34:24.160 に答える
3
  1. #2が現在利用可能な最良のソリューションだと思います。これに加えて、64 ビット システムでは、OS がマッピング用に決して選択しないアドレス (たとえば、Linux では 0x6000 0000 0000 0000) にマッピングを明示的に作成して、OS が最初のマッピングの直後に新しいマッピングを配置できない場合を回避できます。 1。

  2. 単一の munmap 呼び出しで複数のマッピングをアンマップすることは常に安全です。必要に応じて、マッピングの一部をアンマップすることもできます。

于 2013-05-23T05:06:55.200 に答える
3
  1. 可能な場合は、ftruncate() の代わりに fallocate() を使用します。そうでない場合は、ファイルを O_APPEND モードで開き、ゼロをいくらか書き込んでファイルを増やします。これにより、断片化が大幅に減少します。

  2. 利用可能な場合は「巨大なページ」を使用します。これにより、大きなマッピングのオーバーヘッドが大幅に削減されます。

  3. それほど小さくないブロックサイズの pread()/pwrite()/pwritev()/preadv() は、それほど遅くはありません。IO を実際に実行できるよりもはるかに高速です。

  4. mmap() を使用する際の IO エラーは、EIO などではなく segfault のみを生成します。

  5. SQLite WRITE パフォーマンスの問題のほとんどは、適切なトランザクションの使用に集中しています (つまり、COMMIT が実際に実行されたときにデバッグする必要があります)。

于 2015-05-12T04:50:31.527 に答える