背景
GraalJS への切り替え可否を判断するため、同一条件で Rhino と GraalJS の性能比較を実施した。
計測条件
- 実行スクリプト:
test-war/run-jmeter-with-jfr.sh
- シナリオ: Mayaa-Cold(
PERF_MIX_MAYAA_COLD=6)
- 負荷条件: 24 threads / rampup 10s / duration 45s / delay 10s
- 計測日時: 2026-04-25
参考値(今回の実測)
- Rhino
- Throughput: 27,625 req/s
- Avg: 0.624 ms
- P95: 1 ms
- Errors: 0
- GraalJS
- Throughput: 608 req/s
- Avg: 28.9 ms
- P95: 59 ms
- Errors: 0
判断
現時点では GraalJS をデフォルトへ切り替えない(Rhino 継続)。
判断理由
- スループットが大幅に低下(参考値ベースで約 -97.8%)
- 平均・p95 レイテンシが数十倍悪化
- 機能互換よりも運用性能リスクが大きい
想定される主因(仮説)
with + ProxyObject による動的スコープ解決コスト
- Java <-> JavaScript 境界の変換コスト
- スレッド単位コンテキスト運用に伴うウォームアップ分散
- JSR-223 の Bindings / eval オーバーヘッド
再評価条件
- スコープ解決方式の見直し(
with 依存の低減)
- 境界変換回数の削減
- 同条件ベンチマークで有意な性能改善が確認できた場合
各要因の詳細
-
with + ProxyObject の動的解決コスト
with は識別子解決を動的にしてしまうため、エンジンが「この変数は常にこの場所」と固定最適化しにくくなります。
さらに ProxyObject を挟むと、プロパティアクセスごとにホスト側コールバック(getMember/hasMember/putMember)が発生し、JS内の単純参照より重くなります。
テンプレート実行は参照回数が多いので、1回あたりの小さい差が総和で大きくなります。
-
Java <-> JS 境界往復コスト
GraalVMでは、JavaオブジェクトをJSから触るたびに相互運用レイヤが介在します。
今回の構造だと、AttributeScopeアクセス、値変換、同期処理で境界を何度も跨ぐため、CPU時間と割り当てが増えやすいです。
特に「大量の小アクセス」を繰り返すワークロードで効きます。
-
スレッドごとのコンテキスト分散
Tomcatのワーカースレッドごとに実行状態が分散すると、ウォームアップがスレッド単位で進みます。
結果として、単一スレッド長時間実行ほど最適化が乗らず、全体では「常に半分温まった状態」になりやすいです。
短い処理を多数回さばくWeb処理だと不利です。
-
JSR-223 オーバーヘッド
Bindings生成、eval経路、型変換は純粋な埋め込みAPIより不利です。
ただしこれ単体で数十倍差の主因にはなりにくく、上の1〜3と掛け算で効いていると見るのが自然です。
-
Rhino 側の適合度
Rhinoは既存実装で Scriptable スコープチェーンと密接に統合され、現在のMayaa実装に対して極めて低オーバーヘッドです。
つまり「今のアーキテクチャ前提では最短経路に近い」ため、移植直後のGraalJS実装が同じ土俵で勝ちにくいのは合理的です。
要するに、今回の差は「GraalJSが遅い」というより、
「現在のMayaaスコープモデルをGraalJSの相互運用層で再現したときのコスト構造が重い」が主要因です。
背景
GraalJS への切り替え可否を判断するため、同一条件で Rhino と GraalJS の性能比較を実施した。
計測条件
test-war/run-jmeter-with-jfr.shPERF_MIX_MAYAA_COLD=6)参考値(今回の実測)
判断
現時点では GraalJS をデフォルトへ切り替えない(Rhino 継続)。
判断理由
想定される主因(仮説)
with+ProxyObjectによる動的スコープ解決コスト再評価条件
with依存の低減)各要因の詳細
with + ProxyObject の動的解決コスト
withは識別子解決を動的にしてしまうため、エンジンが「この変数は常にこの場所」と固定最適化しにくくなります。さらに
ProxyObjectを挟むと、プロパティアクセスごとにホスト側コールバック(getMember/hasMember/putMember)が発生し、JS内の単純参照より重くなります。テンプレート実行は参照回数が多いので、1回あたりの小さい差が総和で大きくなります。
Java <-> JS 境界往復コスト
GraalVMでは、JavaオブジェクトをJSから触るたびに相互運用レイヤが介在します。
今回の構造だと、AttributeScopeアクセス、値変換、同期処理で境界を何度も跨ぐため、CPU時間と割り当てが増えやすいです。
特に「大量の小アクセス」を繰り返すワークロードで効きます。
スレッドごとのコンテキスト分散
Tomcatのワーカースレッドごとに実行状態が分散すると、ウォームアップがスレッド単位で進みます。
結果として、単一スレッド長時間実行ほど最適化が乗らず、全体では「常に半分温まった状態」になりやすいです。
短い処理を多数回さばくWeb処理だと不利です。
JSR-223 オーバーヘッド
Bindings生成、eval経路、型変換は純粋な埋め込みAPIより不利です。
ただしこれ単体で数十倍差の主因にはなりにくく、上の1〜3と掛け算で効いていると見るのが自然です。
Rhino 側の適合度
Rhinoは既存実装で Scriptable スコープチェーンと密接に統合され、現在のMayaa実装に対して極めて低オーバーヘッドです。
つまり「今のアーキテクチャ前提では最短経路に近い」ため、移植直後のGraalJS実装が同じ土俵で勝ちにくいのは合理的です。
要するに、今回の差は「GraalJSが遅い」というより、
「現在のMayaaスコープモデルをGraalJSの相互運用層で再現したときのコスト構造が重い」が主要因です。