コンパイラ型言語において、動的に生成した処理を実行したいという状況がある。例えば、
- 言語処理系
- DBに格納された複雑な条件を元にしたフィルタ処理
- 遺伝的プログラミング
Javaには以前からJavaScriptエンジン(Rhino)が同梱されていたが、Java 8からはNashornと呼ばれる高速化されたエンジンになった。従来よりも圧倒的に速いらしいので、動的コード生成が必要な場所で使えるかどうか、パフォーマンス特性について調べてみた。
結論としては、
- 関数の実行速度は超高速。Scalaでナイーブに実装したインタプリタより速い。
- かわりに関数定義が遅い(すごく)
- 動的に生成したい処理が小数で、評価回数がすごく多い場合はインタプリタ作るよりもJSに変換してNashornで実行したほうが高速。
- 関数定義/実行比が大きい場合は、オーバヘッドがあるので自分でインタプリタ書いたほうがいい。
以下詳細。ベンチマークコードはこちら
関数の実行速度はすごい
適当にでっち上げたLisp風言語インタプリタもどきと比較して、同内容の処理が1.5 〜 2倍速い。
1 2 3 4 |
|
NeiveがScala、下の二つがJS。律儀に関数呼び出ししても1.5倍、+に展開すると2倍速い。
NativeはふつうにScalaの関数オブジェクトで書いたやつ。静的に定義可能な処理は静的に定義したほうがいいのがわかる。
関数定義は遅い
1 2 |
|
(cached)
は同一の関数定義を1000回繰り返したケース。(uncached)
は一回ごとに関数定義内の定数を変えている。
関数定義があまりにも遅いのでキャッシュが実装されているらしいことが伺える。
適用例
Scalaによる遺伝的プログラミング実装に、ツリーの内容をJSに変換する最適化処理を実装してみたんですが、このユースケース(関数1k回定義→600k回実行くらい。実行速度はナイーブ実装で1ms以下)だと10倍くらい遅くなるので使えななかったです(´・_・`)