原文はこちら。
The original article was written by Mpirvuca Pirvu (Software Developer, IBM).
https://blog.openj9.org/2020/02/11/a-glimpse-into-performance-of-jitserver-technology/
前回のエントリでは、リモートプロセスにオフロードすることで、JVMをJITコンパイルの悪影響(JITによるCPUやメモリの使用量による干渉)から解放するJITServerテクノロジーをのぞき見しました。
Free your JVM from the JIT with JITServer technology
https://blog.openj9.org/2020/01/09/free-your-jvm-from-the-jit-with-jitserver-technology/
https://logico-jp.io/2020/12/17/free-your-jvm-from-the-jit-with-jitserver-technology/
このエントリでは、このテクノロジーをテストしてみましょう。Liberty application server上で動作するJava EEベンチマークのコンテキストで、いくつかの主要なパフォーマンスメトリックを計測してみます。
Experimental setup
JITServer テクノロジーでは、2 つの要因が逆方向に作用します。
- クライアントJVMでの全体的なCPUとメモリの消費が削減される(JITのオーバーヘッドはサーバに移される)
- リモートでのJITコンパイルはネットワークのレイテンシの影響を受けるため、時間がかかると予想される
このトレードオフに対処するために、我々の実験では2つの異なる使用環境を扱う必要があります。
- リソース制約のある環境(Javaアプリケーションを実行するコンテナに小さなCPUやメモリといった制限がある場合)
このような環境では、JITコンパイルの悪影響がより顕著に現れ、JITServerがこのような環境で優位に立つことを期待しています。 - CPU とメモリの制限がゆるい環境
この場合、通常は非同期実行される JIT コンパイル活動でアプリケーションは妨げられることが少ないため、JITServerは、ネットワークレイテンシの悪影響が JIT コンパイルをオフロードすることで得られるメリットを上回るため、いくつかのパフォーマンス指標では若干パフォーマンスが低下することが予想されます。
利用しようとしている2種類のJava EEベンチマークはAcmeAir(航空券予約システムをシミュレートするベンチマーク)とDaytrader7(株取引のオンラインプラットフォームの実装)です。
AcmeAir
https://github.com/blueperf/acmeair-monolithic-java
Daytrader7
https://github.com/wasdev/sample.daytrader7
両者ともWebSphere LibertyのWeb Profileベースイメージ(19.0.0.9-webProfile7)上にビルドされているDockerコンテナで動作します。
websphere-liberty official image
https://hub.docker.com/_/websphere-liberty
open_libertyでも動作するでしょうが、便宜上、自由に利用できるwebpshere-livertyのイメージを選択しました。
Open Liberty official image
https://hub.docker.com/_/open-liberty
今回のベンチマークでは、installUtility
というツールで簡単にインストールできるLibertyの古い機能が必要です(このinstallUtility
ツールはwebsphere-libertyでは利用できますが、open-libertyでは利用できません)。
下の図1に示すように、system-under-test (SUT) のマシンは、CPUはIntel Core i7 6700K、16GBのRAM、Ubuntu 16.04が動作する、4コア(8HWスレッド)のデスクトップマシンです。 データベースエンジン(AcmeAirはMongoDB、Daytrader7はDb2)は、第二のデスクトップマシン上で実行され、JITServer(有効時)とSUTに負荷をかけるためのJMeterアプリケーションは、第三のデスクトップマシン上で実行されます。すべての実験において、共有クラスキャッシュ(SCC)技術を有効化し、別のDockerボリュームを使用して永続化しました。
Class sharing in Eclipse OpenJ9
https://developer.ibm.com/tutorials/j-class-sharing-openj9/

Start-up experiments
起動には AcmeAir ベンチマークを使用しています。表1に示すように、リソースに制約のある環境 (1 vCPU、150MB) では、JITServerテクノロジーにより起動時間は6%改善できます。しかし、十分な計算資源が利用可能な場合 (4 vCPU、512 MB) では、リモートコンパイルの高いレイテンシが支配的な要因となっているため、JITServerにより起動時間が12%も遅くなっています(コンパイルされたコードの利用が遅れると、アプリケーションはインタープリタでより多くの時間を費やすことになるため、起動時間にもろに影響します)。
–cpus=1 –memory=150m | –cpus=4 –memory=512m | |
OpenJ9 | 3367 ms | 2141 ms |
JITServer | 3155 ms (-6%) | 2394 ms (+12%) |
JITServer -Xjit:enableJITServerHeuristics | 3261 ms (-3%) | 2267 ms (+6%) |
ネットワークレイテンシの影響を最小化する試みとして、JITServerには低コストなコンパイルをクライアントJVMでローカルに実行し、高コストなコンパイルだけをJITServerにオフロードするモードがあります。-Xjit:enableJITServerHeuristics
でこのモードを有効にすると、4つのvCPUを使用する場合で、起動時間の遅れを+12%から+6%に縮小することができました。このモードの欠点は、リソースに制約のあるシナリオでは、JITServerの起動の優位性が6%から3%に減少することです。ここはもっと改善の余地があると考えています。
Throughput experiments
図2と図3では、AcmeAirアプリケーションに負荷がかかった場合のスループットを示しています。リソースに制約のある環境(図2参照)では、JITServerテクノロジー(青線)を使うと、コールドラン(共有クラスキャッシュが空の場合の実行)では立ち上げが大幅に改善されます。ウォームランの場合コールドランほどではありませんが、やはり少々改善されます。後者の場合、JITは共有クラスキャッシュから多くのAOTコンパイル済みクラスをロードしますが、これは非常に高速かつ低コストな処理です。そのため、JITServerを使っているかどうかに関わらず、最初のうちは立ち上がりのカーブがかなり急なものになっています。そして、それらのAOT本体の再コンパイルがコンパイル時間を支配し始めると、素のOpenJ9は再び遅れ始め、~180秒後に追いつきます。コールドランとウォームランの両方において、コンパイルスレッドが使うCPUは、JITServerによって大幅に削減されています (60-75%) 。理論的には、JITServerを使用している場合、JVMはコンパイルにCPUを消費しないはずですが、そうはなっていません。なぜなら、クライアントJVMのコンパイルスレッドはJITServerと通信しなければならず、結果として結構な量のリソースを使うからです。


Figure 2. AcmeAir throughput in a resource constrained environment


Figure 3. AcmeAir throughput in a “generous” configuration
コンピュートリソースが潤沢にある環境の場合(図3参照)、JITServerの利点は顕著に減少します。4 vCPUを自由に使える場合、JVMは比較的迅速にコンパイルリクエストのバックログを解決し、JITコンパイルの悪影響を感じるのは短期間ですに過ぎません。より大きなアプリケーション(たくさんのメソッドやクラスをコンパイルしなければならない場合)、JITServerから受ける便益はより大きなものになる可能性があります。これは実際にDaytrader7アプリケーションの場合で、4つのvCPUを使用しても、OpenJ9の場合だと、JITServerで達成したスループットのレベルに到達するのに約200秒かかります(図5参照)。


Figure 4. Daytrader7 throughput in a resource constrained environment


Figure 5. Daytrader7 throughput in a “generous” configuration
Effect of network latency
前回のエントリで説明したように、1回のJITServerコンパイルの間に、多くのメッセージがクライアントJVMとJITServer間で交換されるため、ネットワークレイテンシはJITコンパイルの時間に直接響いてきます。
Free your JVM from the JIT with JITServer technology
https://blog.openj9.org/2020/01/09/free-your-jvm-from-the-jit-with-jitserver-technology/
https://logico-jp.io/2020/12/17/free-your-jvm-from-the-jit-with-jitserver-technology/
ネットワークレイテンシの効果を測定するために、追加のネットワークスイッチをSUIマシンとJITServerのマシン間に配置しました。結果として、pingのラウンドトリップ時間は250μsecから350μsecに増大し、このような小さな変化でさえ、AcmeAirの立ち上がりの曲線に目に見える劣化をもたらしました(図6参照)。このように、ネットワークレイテンシがミリ秒単位になる場合、おそらくJITServer技術には適していないと思われます。

Increasing the density of applications with JITServer
メソッドのコンパイル中、JITコンパイルスレッドは内部データ構造のためにメモリを確保する必要があります。このメモリはコンパイルの最後に完全に OS に解放されます。しかし、図7に示すように、この一時的なメモリ消費は、フットプリントのためにウォーターマークを押し上げることが多々あり、結果として必要以上に大きなコンテナができてしまう可能性があります。

この点を検証するために、OpenShiftのドキュメントで推奨されているように、QoS保証を維持するためにスワップ領域を使用しない構成でDaytrader7の実験を実行しました(OpenShiftのドキュメントによると、「swap メモリーは、OpenShift Container Platform クラスターに追加されるすべての RHEL マシンで無効にされます。これらのマシンで swap メモリーを有効にすることはできません。」とあります)。
Configuring your cluster to place pods on overcommitted nodes
https://docs.openshift.com/container-platform/4.6/nodes/clusters/nodes-cluster-overcommit.html
2.5. POD のオーバーコミットノードへの配置
https://access.redhat.com/documentation/ja-jp/openshift_container_platform/4.6/html/nodes/nodes-scheduler-overcommit
Adding RHEL compute machines to an OpenShift Container Platform cluster
https://docs.openshift.com/container-platform/4.6/machine_management/user_infra/adding-rhel-compute.html
7.1. RHEL コンピュートマシンの OpenShift Container Platform クラスターへの追加
https://access.redhat.com/documentation/ja-jp/openshift_container_platform/4.6/html/machine_management/user-provisioned-infrastructure#adding-rhel-compute
私たちの実験では、(Dockerオプションの -memory=
を使い)アプリケーションがOut-Of-Memory killerによって終了されることなく実行できるまで、コンテナのサイズを徐々に大きくしていきました。OpenJ9ではDaytrader7を10分間クラッシュせずに実行するには最低でも400MB必要ですが、JITServer技術ではこの制限を310MBまで減らすことができることがわかりました。
Teaching the OOM killer about control groups
https://lwn.net/Articles/761118/
しかし、実際には、コンパイル作業が予測できないため、ユーザーは過剰にプロビジョニングしてしまう可能性が高いです。OpenJ9では、JITスレッドには256MBまでのメモリを割り当てることができます。コンパイルがこの制限に近づく可能性を考慮して、Daytrader7が定常状態で約310MBで動作する場合、コンテナの制限を約550MBに設定する必要があります。リスクの選好に依存しますが、複数のコンパイルスレッドが同時に限界に近づく状況を避けるために、この限界をさらに大きくできますし、大きくしておくべきです。JITServerのシナリオでは、安全のために20MBを追加して合計330MBとすると、JITServerでは
(550 – 330) / 550 = 0.4
と40%小さいコンテナを使用することができます。これによりアプリケーション集積度を40%高め、40%分コストを削減できます。
Conclusion
JITコンパイラは、長時間実行でJVMのパフォーマンスを向上させますが、機能するためにCPUとメモリを必要とするため、Javaアプリケーションの円滑な実行を妨げる可能性があります。JITコンパイルをリモートのJITServerプロセスにオフロードすることで、この干渉を緩和し、Javaアプリケーションのパフォーマンス特性(起動時間、立ち上がり時間、ピーク時のメモリ使用量など)を改善できます。JITServerテクノロジーがもたらす改善点は、多くのメソッドをコンパイルし、リソースに制約のある環境で実行される大規模なJavaアプリケーションにとってより重要です。一方で、ネットワーク通信に依存しているため、JITServerは信頼性の低い、または高レイテンシのネットワーク接続を持つ環境にはあまり適していません。