VMのJIT化の恩恵を十分に受けるには、あらかじめJITを想定したVMの構成とコンパイラが必要だよなぁ…
Wiliki:Gauche:VMの最適化:JIT:予備実験
を読んでいて思ったこと。shiroさんが作業して2倍ってことは、俺みたいな平凡人間が仮に作業したとしても大して変わらないというか、逆にオーバーヘッドで遅くなることの方が多いってことなんだろうなぁ…と思った。
そもそも、JITを考えない場合のVMの最適化の手法としては、なるべく1命令での処理を多くする(=オペコードの数を少なく、プログラムの長さを短く)するというのが基本。これは当たり前で、仮想機械レベルでロジックを実行するよりも、仮想機械の柔軟性を損ねない範囲でなるべくコンパイルされたCの関数を実行した方が速いに決まっているから。
だから、GaucheでもRubyでも、命令の定義を別ファイルに書いておいて、それをスクリプトで処理して実際のVMのメインループを生成するようになっている。これだと、管理が簡単になるというメリットの他に、よく使われる命令の並びを1命令にしてしまう事が可能(というか、様々な組み合わせを素早く試すことが可能)になる。これも命令数を減らす工夫。
つまり、スタック操作が少なく、オブジェクトに対する(Cレベルの)関数呼び出しが多くを占めるように最適化されたVM(というかSchemeという言語の実行モデル?)の場合は、もともとネイティブコードで実行される割合が大きい分、JITによる恩恵は少ないってことになるよなぁ(その点、言語レベルでintとかのプリミティブを持つJavaは、JITという面では有利ということになると思われるな)。
もちろん、そのように構成されたVMと命令セットであっても、JITのコンパイルを頑張ればうまく効率の良い機械語に変換できると思うけど、JITなんだからそのコンパイルに時間をかけたら本末転倒だし、コーディングの手間もかけてらんないっていうのが現実。
まとめると、Schemeのような動的な言語において、「JITによる高速化に適したVMの構成」と「VMのみでの高速化に適したVMの構成」っていうのは互いに相反する要素があるってことなのかなぁ。特にSchemeの場合。どうだろう。
だとしたら、JITの恩恵を十分に受けるには、
ような方針が必要かな。つまり、コンパイル時に型推論とかがんばって、なるべくintで計算できるところはintでの演算に落とし込んでしまう。ループとかも。
Google V8の中身はどうなってんだろう。ちょっと興味がわいてきた。
Javascriptなら、整数をループ変数として利用したりっていう、単純な使い方を普通にするから、まだ有利かな。型推論をうまくやれば、Javaに近いように変換できそうな気がする。