ヒープ/オブジェクトシステムのrewriteに向けて
現状のmoshはC++で書かれ、BoehmGCを使用したヒープを持っている。これがポータビリティとかパフォーマンスのような様々な面でボトルネックになっていて、ここを改善するのが非常に重要な課題になっている。
というわけで、ヒープ周りをそろそろ作りなおす時期にあるような気がしている。
戦略
基本的には、先にI/O VM( http://d.hatena.ne.jp/mjt/20110828/p1 )のヒープシステムとして使い、そこで十分な実績を積んでから全体のヒープを置き換えることを目指す。ヒープシステムの置き換えは既存のC++コードを総取替えすることを意味するので、最初からR6RSのプリミティブタイプすべてを実装するよりはリスクの低いところから。。
基本的なモデルはGambitのGCを参照する。このGCはスライド( http://www.iro.umontreal.ca/~gambit/Gambit-inside-out.pdf )で解説されている。MOVABLE/STILL分割をし、必要に応じてFFI参照もサポートする。
重要なポイントは:
- Cで書く
現状のオブジェクトシステムはC++で書かれている。これはメンテナンス性や記述性の面では好ましいが、C++のABIは複雑でJITCから呼びづらいという微妙な事情がある。
Google v8や現在のMoshのように標準ライブラリの多くをC++で持つなら、オブジェクトシステムをC++で書くことにも意義があるが。。
- 高レベルなオブジェクトサポートはSchemeで書く
JITCやAOTCを期待し、レコード型やO(1) stringのようなオブジェクトはScheme側でサポートすることにする。
Cで書かれたオブジェクトシステムは、Pair、Vector、Bytevector程度のプリミティブしか持たない。CharやSymbolを持たないで済む上手い方法を考え中。。
- O(1) stringを捨てる
現状のmoshはO(1) stringのために1 charあたり4 bytesを消費している。ほとんどのシチュエーションではO(1) string accessは必要ない。代わりにUTF-8形式を使用する。
必要に応じて(Scheme側で、)O(1) stringsを実装できるようにする必要がある。
そもそもstring自体をサポートする必要があるかも絶妙なところ。
- Movable objectをサポートする(Copy on Writeをサポートする)
Copying GCでは、そもそもオブジェクトは基本的に移動可能である必要がある。
- 近代的なメモリマネジメント機能を期待する
もうLinuxやFreeBSDのような常識的なOSはSuper-pagesのようなlarge pageを透過的に扱うためのうまい仕組みを持っているため、オブジェクトシステムとしてlarge pagesを積極的に使用することはしない。セクションをlarge pageアラインで取るようにする程度の対処はするかもしれないが。。
当然デマンドページングを期待してOSのmallocは大きな単位でするし、page-fusionを期待して開放されたセクションはmunmapするかもしれない。
TLSもほとんどのOSでサポートされている。
- 32bit Hostをサポートする
↑とは相反するけど、32bit Hostは残念ながらサポートする必要がある。(主にWin32とARMのため)32bit wordと64bit wordの両方を想定し、TAG bitsの配置なども両方で考察する必要がある。
MMUの無いシステムをサポートするかどうかは微妙なポイントになるが、どっちにせよ最初に(メモリをmallocで取る)非MMU版を作るので自然にサポートされる気もする。非MMU版を持っておくのはValgrindのようなヒーププロファイラとの相性という面でも重要になる。
- Pairs/Vectorsを区別し、Uniform Vectorを熱心にサポートする
まだ単なる思いつきだけど、セクションをポインタの有無で完全に分けてしまうほうが効率的な気がしている(BoehmGCで言うとAtomicなオブジェクトと普通のオブジェクトを別々のセクションに格納する)。
これを行うと、構造体はポインタを含むパートと含まないパートの2つに分割されることになる。
(少なくとも手元の想定アプリケーションでは)ポインタとデータが混在する構造体のほとんどは、非ポインタデータ自体がフラグとかデータカウントのような数ワードしか無い。つまり、↓のmulti word objectで十分なことが多い。
Uniform vectorは非常に使い途の多いオブジェクトタイプなので、メモリバッファのタイプ情報はオブジェクトシステムが面倒を見る。
- マルチスレッドサポート
当然マルチスレッドサポートは必要になる。あるセクションにアロケーション可能なスレッドを1つに限定する機能を提供する。
(Mostly) Concurrent GCにするかは絶妙なポイントで、I/O VMはレイテンシが非常に重要なアプリケーションなのでとりあえずサポートする方向。
- その他の機能
FFIオブジェクトのリファレンスカウントとcallback / ヒープのフリーズ / weak reference / ...
悩みどころ
全体的に、このオブジェクトシステムの上にのるVMの構成はまだまだ悩んでいるところ。基本的にはGoogle V8のようなDirect-threadedインタプリタを想定していて、スタックオブジェクトに関してはあまり積極的なサポートを考えていない。
ChickenのようにCheney-on-MTAスタイル(= 継続をすべてスタックに残し、GCで回収する)のVMにするのも悪くないとは思うけど、メモリトラフィック的に大丈夫なのかが不安。
- Multi-worded objectのサポート
ほとんどのシステムはSIMD演算器を備えていて、JITCの出力するコードもこれらを活用する可能性が高い。
問題は、SIMD演算器のメモリシステムは基本的に128bitアラインメントのような複数ワード単位のアラインメントを期待しているため、オブジェクトシステムはこれを考慮してやる必要がある点。
というわけで、オブジェクトの最小サイズをキャッシュラインサイズ(4〜8word程度 = SIMD演算器のロードストアサイズ)まで広げることを考えている。これをするとマークビットを節約できるが、メモリ効率は当然低下する。ポインタのアラインメントは増加するので、TAG bitとして3〜4bit取ることができるようになる。
TAG bitの数にバリエーションが出ることを想定するかどうかは絶妙。
(当初は、積極的にポインタとデータを混ぜることを想定していて、先頭からN wordがポインタという情報もTAG bitに入れようとしていた。)
- 実行可能セクションのサポート
JITCは当然これを必要とするが、オブジェクトシステムでサポートするほど一般化できるかは微妙。。もちろんセクション自体を可変長にして、セクション = MMU Pagesというふうにするのは悪くないアイデアだけど。。