メモリマップドファイルのはなし

Rubyのスレッドはユーザーレベルスレッドなので、CPUがデュアルコアだったとしても、片方しか使わない。
ユーザーレベルスレッドには、たとえば環境に寄らず一定の動作をするとか、スレッドが割り振られるCPUによって動作速度が変わったりしないとか、利点はあるのだが、これからのマルチコア時代にその性能を活かせないのは残念なことだ。
スレッドレベルでの並列処理ができないなら、プロセスレベルで並列処理なんかできないものだろうか。


CPUが1つ完全に余ってると考えれば、Rubyインタプリタをもう一つ動かしちゃっても、何もしないよりは効率も高くなろう。
問題は2つの分離したプロセスをどのように同期させるか、という話だ。
例えばdrbとか使ってもよいが、1台のPCの中でTCP/IPを使って通信するというのも無駄が多い。
そこで、今日はメモリマップドファイルの実験をしていた。
メモリマップドファイルは、基本的にはディスク上のファイルをメモリに展開し、それを直接編集してファイルをいじろう、という仕掛け。
んで、フラッシュするか解放するかでファイルが更新される。
これだけならプロセス間通信に使えるものではないのだが、Win32APIのメモリマップドファイルは(他のOSは知らない)それに名前を付けることができて、その名前で別プロセスから同時にオープンすることができる。
片方が中身を更新したらもう片方が見ているメモリも書き換わる。すなわち、メモリのある範囲を、ファイルを介して直接共有することができる。ディスクキャッシュをマッピングしているような感じだ。
おまけにファイルハンドルを0xffffffffにすればファイルを作らずメモリを確保できるから、もはやメモリマップド「ファイル」ではない。恐ろしい。
具体的にはCreateFileMapping、MapViewOfFile、FlushViewOfFileあたりのAPIを使う。


普通はCで拡張ライブラリを書くところなのだが、最近Win32APIやDLを駆使してRubyだけでAPIをいじり倒すことに凝っていて、今回もそれでチャレンジしていた。
APIを叩いてメモリを複数プロセスで共有することはできた。
ところが、MapViewOfFileはシステムが確保したメモリへのポインタを返してきて、Rubyでバイナリは文字列として扱うから戻り値はStringオブジェクトになるのだが、Rubyのstr_new関数はデータをコピーしてStringオブジェクトを生成するから、Ruby側で取得できる文字列はメモリマップの中身をコピーした別領域になってしまう。
これでは共有メモリを操作することができない。
うーん。
システムが確保したメモリを直接いじる方法が見当たらない。
やはりCで拡張ライブラリを書かねばならないのか。


これが効率よくできれば、メイン(DXRubyを動かすほうのプロセス)とサブ(ゲームのロジックを動かすほうのプロセス)を別々に作りこんで、描画関連処理とゲームの処理を並列実行することができる。
実際には、とにかくやってみないと速くなるかどうかもわからないが、なんにせよマルチコアCPUをRubyで有効活用できるかもしれない可能性のひとつであることは確かだ。
俺としてはRubyオブジェクトを共有メモリに配置して、どっちからでも呼べるよーな仕組みが欲しいところだが、それをどのようにインタプリタが管理するのかとか、GCはどうするのかとか、ややこしい問題が目白押しになりそうだから普通に無理だろう。
そうすると共有メモリに配置するのは単純なデータ、もしくはオブジェクトをマーシャリングしたもの、って感じか。
Rubyから簡単にアクセスできるようなライブラリが構築できれば、なかなか面白いかもしれない。
Rubyがネイティブスレッドに対応してくれればそんな必要もないのだが、早くても数年先だと思うから、なんらかの手を考えたいところだ。