Sub-VMは良くなかった / Precise GCへの道2

週間nmoshという感じではなくなってきてしまったので、どうしようかは考え中。

Sub-VMのまずい点

prev : http://d.hatena.ne.jp/mjt/20130218/p1
GCプレッシャーを避けるために、ヒープシステムを共有しないSub-VMを実装し、リアルタイム処理はSub-VMに任せるという方向でシステムをデザインしてみた。
GCタイミングの制御は明らかな優位点だが、今回のプロジェクトの複雑なパートの殆どはMain-VM(こちらはBoehmGCになる)で実装する必要があり、Main-VMとSub-VMでのコミュニケーションのオーバーヘッドが無視できない問題になった。つまり:

  • SubVMはMainVMとコミュニケーションをしなければならない場合に、GCポーズの影響を依然受ける(= 対向VMGCポーズにいる場合は通信できない)
  • SubVMオブジェクトの構築をMainVMから行うのが重い(= MainVMでのオブジェクト構築中にGCポーズが起こると遅延が発生する)
  • メモリfootprintが大きい(= より多くのMainVM GCが必要になる)

というわけで、Sub-VMのアイデアはうまくフィットしなかった。

Precise GC化の検討

コードベースに大きく手を入れてPrecise GCにすることが望ましいという結論に達したので検討をする。(あと、ネイティブコード部分でValgrindを使いたいという需要にも対応できる。。)
保守的GCを想定したコードをPrecise GCにするのはかなり難しい。やることは:

  1. new(GC)やnew(PointerFreeGC)を適当なマクロに置き換える
  2. GC対象クラスに明示的なgc_mark関数を実装する
  3. デストラクタで明示的にPointerFreeGC領域を開放する
  4. GC対象クラスの参照を明示的にする

...これらを厳密に現状のコードベースに適用するのはどう考えてもゼロから書きなおしたほうが早いので、ちょっとしたズルをする。つまり、GCがトラバース可能なのはヒープのみとし、Cスタック/レジスタはroot setとしない。これにより、GCを使用するC++クラスへのgc_mark関数の実装と、VMレジスタ/スタックの参照の明示化で殆どの対応が完了する。
この限定を行うと変更の多くはtrivialな問題になるが、依然、明示的な開放の追加は難しい問題になる。たとえば、ReaderはバッファをPointerFreeGCで取るので適当なタイミングで開放する必要がある。

Precise GC化とスレッド

ベースとなるGCにはpicogc( http://d.hatena.ne.jp/kazuhooku/20120327/1332813130 )を一旦採用する。
picogcは、確保されたオブジェクトのポインタを全て記録する必要がある(そうでないと、マークされなかったオブジェクトを見つけ出すことができない)。このため、2wordのオブジェクトであるPairを作成する度に、さらに1ワードを消費することになる。これだとメモリがもったいないので、PairとHeapObject(ポインタを含む可能性のあるSchemeオブジェクトはすべてこのどちらかから指されることが保証されている)は専用のアロケータを使ってtrackを不要にする必要がありそう。
また、picogcはマルチスレッドなmutatorをサポートしていないので、このサポートも追加する必要がある。これは地味に難しい。
今回の想定では、"GC安全"な場所、つまり、GCのcollectorを動作させてもよい場所はVMバイトコードの実行が完了した時点に限られる。マルチスレッド環境で、メモリの回収を発動させるためには、全てのスレッドがGC安全な場所を実行している必要がある。
これは、スレッドがブロッキングI/Oや同期オブジェクト待ちのような処理を行っている場合に問題になる。単純に実装すると、これらの同期待はGC安全な場所では実行されない(= C++関数の先で行われる)ため、I/OのブロックによってGCの実行がブロックされることになってしまう。
BoehmGCでは、CPUレジスタやCスタックがroot setの一部になっているため、単純に割り込みを行ってsyscallを中断することでGCを行うことができる。