Multi-Tier Compilation in GraalVM

原文はこちら。
The original was written by Aleksandar Prokopec (Principal researcher at Oracle Labs).
https://medium.com/graalvm/multi-tier-compilation-in-graalvm-5fbc65f92402

Introduction

このブログ記事では、Graal.js、TruffleRuby、Java on TruffleなどのGraalVMのTruffle言語における多層コンパイルのパフォーマンス上のメリットを調査し、多層モードがどのように機能するかを理解するために、簡単な裏技を紹介します。

また、4月22日午後5時(CET)、以下のURLでTruffle言語の多層コンパイルについてお話しました。

Twitch
https://www.twitch.tv/thomaswue

GraalVM 21.1では、Truffleで実装された言語向けに多層コンパイルという新機能を導入しました。多層モード(multi-tier mode)はウォームアップの動作を改善し、特に大規模なコードベースで構成されるプログラムに有効で、起動時間を30%~50%改善します。多層モードの基本的な考え方は、コール・ターゲットのコンパイルを、高速な第1層と低速な第2層の2つの階層に分けることです。第1層のコンパイルでは最適化の数は少ないもののすぐに利用できるため、プログラムの起動が速くなります。第2層のコンパイルでは、最適化の数が多いためコンパイルの時間を要しますが、プログラムが最終的にピークパフォーマンスに達することを保証します。21.1リリースの時点で、GraalVMの多層コンパイルはデフォルトでオンになっています。

多層コンパイルの動作を制御する実験的なオプションがいくつかあります。通常は、デフォルトの値が最も効果的ですが、より冒険的なユーザーは、自分のワークロードでこれらのオプションを試してみるとよいでしょう。

Option用途
MultiTier多層コンパイルを有効化する。
(GraalVM 21.1以降、デフォルトでtrue)
FirstTierCompilationThreshold第1層のコンパイルがスケジューリングされるまでに、コールターゲットで実行されるコールまたは ループ反復の回数
LastTierCompilationThreshold第2層のコンパイルがスケジューリングされるまでに、コールターゲットで実行されるコールまたはループ反復の回数
FirstTierInliningPolicy第1層のコンパイルで使用するインライン化ポリシーを決定する。
GraalVM 21.1では、デフォルトで「TrivialOnly」に設定されており、小さなメソッドのみがインライン化される。
FirstTierUseEconomy高速コンパイルで最小限の最適化を使用するかどうかを決定する。
(デフォルトでtrue)
FirstTierBackedgeCountsループの反復を第2層のコンパイルのしきい値に向かってカウントするかどうかを決定する。(デフォルトでtrue)
SingleTierCompilationThreshold多層コンパイルが無効化されているときのコンパイルのしきい値を決定する。CompilationThresholdと同じ効果があるが、CompilationThresholdは現在非推奨であり、事実上このオプションに名前が変更された。

Improvements in startup times

多層コンパイルの利点を示すために、まずウォームアップカーブの概念を紹介します。プログラム内で同じワークロードを長時間実行すればするほど、そのプログラムは徐々に高速になる傾向があります。それは、プログラムのコードが最初はコンパイルされておらず、プログラムがVM内で実行されている間にコンパイルされるためです。プログラムが早期に速くなればなるほど、よいウォームアップです。ウォームアップカーブとは、プログラムの実行速度が時間とともにどのように変化するかを特徴づけるものです。この記事では、VMが起動してからの総実行時間をパフォーマンス指標としています。総実行時間が短いほど、良好なウォームアップです。

ウォームアップカーブの例を見てみましょう。ウォームアップカーブを描くためには、定義された量の作業を実行後に、定期的に経過時間を出力できるプログラムを選択する必要があります。その方法として、ベンチマークを選び、それをN回繰り返して、そのたびに経過時間を記録するという方法があります。多層コンパイルの利点を分析する際には、GraalVMがより多くのコールターゲットをコンパイルする必要があるため、ウォームアップに時間がかかるような、比較的コード量の多いプログラムが面白いでしょう。そこで、Web Tooling Benchmark Suiteからいくつかの大規模なベンチマークを使用し、Node.jsのコードを実行するJavaScriptエンジンであるGraal.jsで実行することにしましょう。

ウォームアップカーブの例を見てみましょう。ウォームアップカーブを描くためには、定義された量の作業を実行後に、定期的に経過時間を出力できるプログラムを選択する必要があります。その方法として、ベンチマークを選び、それをN回繰り返して、そのたびに経過時間を記録するという方法があります。多層コンパイルの利点を分析する際には、GraalVMがより多くのコールターゲットをコンパイルする必要があるため、ウォームアップに時間がかかるような、比較的コード量の多いプログラムが面白いでしょう。そこで、Web Tooling Benchmark Suiteからいくつかの大規模なベンチマークを使用し、Node.jsのコードを実行するJavaScriptエンジンであるGraal.jsで実行することにしましょう。このベンチマークスイートを修正して、スイートの起動開始からの総経過時間を収集するようにしました。

Web Tooling Benchmark
https://github.com/v8/web-tooling-benchmark

まずはWeb Toolingのbabelベンチマークを10回繰り返します。VMを開始してからn回目の繰り返しが終わるまでの経過時間の合計をプロットします。この実験を2回実施します。1回は多層コンパイルなし、もう1回は多層コンパイルありで行います。この実験は、次のようなコマンドで行います(-iパラメータは、VMを終了する前にベンチマークを何回繰り返すかをハーネスに伝えるために使います)。

<graalvm-home>/bin/node --experimental-options --engine.MultiTier=false src/cli.js -i 10 -b babel
<graalvm-home>/bin/node --experimental-options --engine.MultiTier=true src/cli.js -i 10 -b babel

結果、以下の2種類のウォームアップカーブが得られました。

多層コンパイル利用時は,プログラムが最初の反復の終了に到達するまでの時間が約33%短くなり、10 回目の反復の終了に到達するまでの時間が約43%短くなりました。このグラフから、2つのウォームアップカーブが放射状に広がっていることもわかります。babelベンチマークでは、第1層のコンパイルのほうがウォームアップカーブの傾きを小さくするのが早く、つまり各反復の所要時間が短くなります。単層コンパイルでは、(7回目の反復後に見られるように)最終的にカーブの傾きが徐々に減少しますが、これにはより長い時間がかかります。

ウォームアップカーブが作業負荷の繰り返しごとの総経過時間を表している場合、カーブの傾きはプログラムの実行速度を表しており、傾きが小さいほどプログラムが高速であることを示しています。

コードサイズがかなり大きい他のいくつかのベンチマークでもこの実験を実施できます。espreeesprimaの両ベンチマークでは、2回の実行時のウォームアップカーブの傾きは大きく広がっていません。これは、これらのベンチマークでは、(初期遅延の後)単層コンパイルでの実行が比較的早くピーク性能に到達することを示しており、さらにこれらのベンチマークはbabelよりもコード量が少ないことを意味しています。しかし、多層コンパイルでの実行は1回目の反復ですでに先行しており、絶対的な時間差はその後も変わりません。

一般に、多層コンパイルによる具体的なパフォーマンスの向上は、プログラムの特性やVMを実行するCPUなど、いくつかの要因に依存するため、状況によって変わります。例えば、acornベンチマークでは、2つのウォームアップカーブの違いはそれほど顕著ではありません。

“One-Shot” Metric

ウォームアップカーブには、プログラムの実行状況に関する多くの情報が含まれていることにお気づきでしょうか。例えば、各反復処理の後の時間、プログラムの速度の変化割合(ウォームアップカーブの傾き)、あるいは後述するように実行速度の潜在的なスパイクなどです。このようなことから、VMの異なる実行の比較は非常に困難です。しかし、ウォームアップの特定の側面を捉えるメトリックに限定して、他のすべてを無視したい場合もあります。例えば、one-shotメトリックを、ベンチマークの1回目の繰り返しを完了するのにかかる総時間と定義することができます(これは、ウォームアップ曲線の最初の点のy座標の値です)。

one-shotメトリックは、VM始動からワークロードの1回目の繰り返し終了までの時間です。

one-shotメトリックは特に興味深いメトリックです。というのも実行開始時のダイナミクスを捉えることができるためです。多くの実用的なシナリオでは、コマンドラインツールや単一のユニットテストの実行など,短時間で実行されるプログラムに関心があります.他のシナリオでは、プログラムが最高のピーク性能を達成するのはずっと後になるとしても、できるだけ早くまともな実行性能を達成することを期待しています。

次の図は、前章で紹介したacornベンチマークで、多層コンパイルを行った場合と行わなかった場合のone-shotメトリックを比較したものです。

前述のウォームアップカーブはすべて、16コアのRyzen 5950x CPUを使用し、周波数を2.8GHzに固定して作成しました。また、これらのベンチマークは、デュアルソケットのXeon E5-2699 v3の構成でも実行しました。Xeon E5-2699 v3はCPUコア数が44個であるため、GraalVMはより多くのコンパイラスレッドを生成します。その結果,最初の反復処理ではより多くの第1層コンパイルが実行され、その中には性能上重要なものもあるため、単層と多層のコンパイルモードの差は,Ryzenマシンに比べてデュアルXeonでより大きくなりました。

Other GraalVM languages

one-shotメトリックの概念を定義したところで、今度はウォームアップカーブではなくone-shotメトリックを見て、他のGraalVM言語のウォームアップの挙動を調べてみましょう。

RubyのAsciidoctorライブラリは、AsciiDoc形式のテキストをPDFなどの形式に変換します。asciidoctor-convertベンチマークでは、大量のAsciiDocテキストを変換しています。以下のグラフは、多層コンパイルの有無時におけるTruffleRubyのone-shotメトリックを、invokedynamicバイトコード命令を使用するJRubyの実装と比較したものです。

one-shotメトリックでは、多層コンパイルの場合は単層コンパイルの場合よりも約 33%速く、JRuby よりも 60%高速です。Asciidoctorを使ってファイルをロードするだけのasciidoctor-load-fileと呼ばれる(コードサイズの合計では)より小さなベンチマークの場合が以下です。ここでは、one-shotメトリックの差は先ほどよりも小さいですが、それでもまだ目立っています。

最後に、ボットが囲碁をプレイする「rubykon」という大規模なベンチマークを検証します。

Rubykon
https://github.com/PragTob/rubykon

TruffleRuby は、すべての Ruby 実装の中で最高のピーク性能を持っていますが、ウォームアップが特に悪いことは以前に実証されています。

THE GREAT RUBYKON BENCHMARK 2020: CRUBY VS JRUBY VS TRUFFLERUBY
https://pragtob.wordpress.com/2020/08/24/the-great-rubykon-benchmark-2020-cruby-vs-jruby-vs-truffleruby/

以下では、TruffleRuby のウォームアップ性能が大幅に向上したことを示しています。
多層コンパイルを有効にした TruffleRubyでのone-shotメトリックは、MRI と比較して約 2.75 倍、JRuby と比較して 2.4 倍高速です。

次に、GraalVMのTruffleフレームワークを使用したJavaの実装であるEspressoエンジン上で、Scalabenchスイートからのいくつかのベンチマークの起動を検証します。scalacベンチマークはコードサイズが特に大きく、多層コンパイルによりone-shotの時間が41%削減されました。

scaladocおよびscalaxbベンチマークでは、以下のグラフからわかるように、多層コンパイルによってone-shotメトリックの値がそれぞれ 39% および 45% 削減されています。

GraalVM開発の一環で、これらのベンチマーク(および他のいくつかのベンチマーク)のウォームアップ性能を定期的に追跡し、多層コンパイルの最適化に役立てています。次のパートでは、GraalVMでウォームアップを改善した様々な側面について簡単に説明します。

Look under-the-hood

ウォームアップのパフォーマンスには、さまざまな要因があります。これを確認するためには、まず、プログラムが完全に「ウォームアップ」されたこと、言い換えれば、ピークパフォーマンスに達したことの意味を考えてみる必要があります。プログラムがウォームアップが完了した時点を、後続のコンパイルでプログラムの性能がそれ以上向上しないポイントとして定義できます。ここでは、acornベンチマークのピークポイントを、n回目の反復の時間を追跡するウォームアップカーブで示しています(これは、経過時間ウォームアップカーブの微分です)。

このウォームアップカーブを見ると、ベンチマークの繰り返しに必要な時間がどんどん短くなっていくのがわかります。時折段差があって(通常はGCによるもの)、繰り返し時間は少し上がりますが、全体的な傾向は下降しています。ピークを過ぎると、それ以降、繰り返しの所要時間が短くなることはありません。

ウォームアップ性能に寄与する要因は、ピークパフォーマンスまでのプログラムの実行に影響を与えるすべてのものです。これらの要因によって、ウォームアップカーブの形状やピークポイントの位置が決まります。そのいくつかを見てみましょう。

Interpreter impact

定義より、ピークポイントまでの実行では、コードの一部はすでにJITコンパイラによってコンパイル済みであり、一部はまだインタプリタによって実行されています。先ほどのグラフに重ね合わせて、インタプリタによる実行コードとコンパイル済みコードの間の時間分布は以下のようになります(この分布は測定が難しく、以下は大まかな推測に過ぎないことに注意してください)。

先ほどの図のインタプリタとコンパイルの時間は大まかな推測ですが、一つだけ確かなことがあります。それは、コンパイルされたコードに費やされた時間は、ピーク時の時間を超えないということです(これは一般的に、コンパイルされたコードのセットが時間の経過とともに増加するだけだとの仮定が成り立ちます)。明らかに、ウォームアップの初期段階におけるプログラムのパフォーマンスは、インタープリタに費やした時間に支配されています。

インタープリタの性能を向上させると、繰り返し時間のウォームアップ曲線のポイントが下向きになり、ピークポイントが左に移動します。

そこで、GraalVMチームはこの数ヶ月間、個々の言語のインタープリタ性能の向上に注力しました。いくつかのベンチマークで、またいくつかのGraalVM言語で、インタプリタのみの実行モードのパフォーマンスを追跡しました。最も顕著な改善はTruffleRubyで行われ、deltabluerichardsの両ベンチマークで約20-25%の性能向上が見られました。以下のグラフは、2021年1月22日以降の毎日のインタープリター・ベンチマークの実行結果を示しています。

GraalJSでは、インタプリタ性能の向上はTruffle Rubyに比べてやや小さいですが、それでも目立っています。以下のグラフは2020年12月以後でのSieve-of-Eratosthenesベンチマークのパフォーマンスの変化を示したものです。

他のインタープリタのほとんどが比較的成熟しているのに対し、GraalWasmはGraalVMファミリーの中で最も若い製品です。昨年、GraalWasmインタープリタにはいくつかの明らかな改善機会がありました。以下のグラフは、Siev-of-EratosthenesベンチマークのWebAssemblyバージョンでのパフォーマンスの変化を示しています。2020年11月に2つの急激な改善が見られますが、これについては別のブログ記事で詳しく説明します。

Compilation time

ウォームアップの動態に関わるもう一つの要因は、コンパイル時間です。コンパイラが高速であればあるほど、プログラムの性能は早くピークに達します。

コンパイラの性能が向上すると、ピークを含めたウォームアップカーブのポイントが左に移動します。

最適化コンパイラを高速化するには、一般的に2つの方法があります。1つ目の方法は、コンパイラの実装を検査し、コンパイラの性能が最適ではないコードの場所を改善することです。プログラムが最適化されていないのであれれば、どんなプログラムでも最適化できます。2つ目のアプローチは、コンパイラが行う最適化の量を減らすことです。これにより、最適化コンパイラは高速になりますが、同時にピーク性能も低下します。このように、ピークポイントが左に移動する一方で、最適化の量を減らしてコンパイラを高速化すると、ピークポイントが上に移動することがあります。このことは、次の図のウォームアップカーブからわかります。ここでは、最適化のフルセットを使用したGraalコンパイラ(オレンジ)と、最適化を減らしたセット(青)で、acornを2回実行しました。青色の曲線の方が早くピークに到達していますが、ピーク時のパフォーマンスは最適ではありません。

そこで、両方の実行モードの利点を組み合わせて、より良いウォームアップカーブを実現する方法を考えてみましょう。最初にすべてのコードを少ない最適化でコンパイルして、コンパイルを高速化し、できるだけ早くインタプリタモードから抜け出すようにします。これを第1層のコンパイルと呼びます。しかし、ピーク時のパフォーマンスを向上させるためには、後からより多くの最適化を施したコードをコンパイルする必要があります。これを第2層のコンパイルと呼びます。このようにして、プログラムの開始時にウォームアップカーブを下方に押し下げますが、ピークポイントは上方に押し上がりません。いつ、どのコードを第1階層のコンパイルから第2階層のコンパイルに切り替えるのか、その仕組みは微妙ですが、これが基本的な考え方です。

現在のTruffleの多層コンパイルの実装では、第1層のコンパイルはコンパイルの閾値が低く、他の関数やメソッドのインライン化はほとんど行われず、コンパイラのバックエンドは最も重要な最適化と変換のみを行います。第1層のコンパイルにかかった時間を見積もるためには、第2層のコンパイルを無効にする必要があります。そのためには、ちょっとしたトリックを使います。第2層のコンパイルの閾値を非常に高い値に設定すればよいのです。これを実現するコマンドの例を示します(繰り返し回数を200回に設定していることに注意してください。これは、プログラム内のすべてのコールターゲットがそれよりもずっと前にコンパイルされることがわかっているからです(それを確認するためにコンパイルをトレースしました)。

<graalvm-home>/bin/node --experimental-options --engine.MultiTier=true --engine.LastTierCompilationThreshold=1000000000 src/cli.js -i 200 -b acorn

最後の層のコンパイルのしきい値をこのように高い値に設定することによって、第2層のコンパイルが決して呼び出されません。このように高いコールカウントに達することは、大規模なプログラムでは珍しいことであり、あるコールターゲットのコールカウントが時折この値に達したとしても、ほとんどの場合は単なるノイズであるはずです。

これに加えて、-Dgraal.Timeオプションを使用して、VM終了時にコンパイル時の内訳をダンプすることにします。

<graalvm-home>/bin/node --experimental-options --engine.MultiTier=true --engine.LastTierCompilationThreshold=1000000000 --vm.Dgraal.Time= src/cli.js -i 200 -b acorn

PartialEvaluationTime_AccmGraalCompiler_Accmの両エントリは、Truffleの部分評価に要した時間、Graalコンパイラ自身が要した時間を測定します。これらの両時間の合計がおおよその総コンパイル時間です。また、第2層のコンパイルのみを実行した際のデータ、多層コンパイルした場合のデータも収集します。

<graalvm-home>/bin/node --experimental-options --engine.MultiTier=true --vm.Dgraal.Time= src/cli.js -i 200 -b acorn

前述のグラフから、最適化を減らした場合のウォームアップカーブがより早くピークに達する理由は明らかです。これは、コンパイルに費やされる時間が全体的に少ないため、プログラム実行後の短い期間により多くのコールターゲットがコンパイルされることを意味します。さらに、先ほどのグラフで興味深いのは、多層のコンパイル時間が、第2層のみのモードで費やす時間よりもわずかに短いことです。もし、多層モードでコードが2回(第1層で1回、第2層で2回)コンパイルされるのであれば、合計時間は最初の2本の棒グラフの合計になると思いますよね?でもそうではありません。なぜなら、多層モードの第2層のコンパイル閾値は、非多層モードの第2層の閾値よりもはるかに大きいからです。つまり、多層モードでは、コードが非多層モードの場合よりも大幅にホットな場合にのみ、第2層でコンパイルされるということです。なぜこのようなことが起こるのかを考えてみましょう。第2層のみのコンパイルでは、たとえ各コールターゲットを効率的にコンパイルすることがピーク時のパフォーマンスにとってそれほど重要ではないとしても、可能な限り早い段階でインタープリタからすべてのコールターゲットを取り除く必要があります。このように、第2層のみのコンパイルでは、最初の短い期間だけホットになるコールターゲット(初期化コードなど)であっても、高価な最適化を行ってもほとんどメリットがないため、過剰なコンパイル予算が費やされます。多層モードでは、第1層のコンパイルがすでにコールターゲットをインタプリタ実行から取り除く作業を行っているため、第2層(すなわち最適化を多用する)のコンパイルは通常、本当に頻繁に起動されるコールターゲットにのみ焦点を当てる必要があります。このように、acornでは同じピーク性能が得られましたが、コンパイル時間はさらに短くなることがわかりました(この効果はベンチマークの仕様に依存することに注意してください)。

次に、これら3つのバリエーションについて、acornの経過時間のウォームアップカーブを考えてみましょう。第1層のみのコンパイル(青)は、非常に早く妥当な性能に達しますが、その後の傾きは改善されないことがわかります。第2層のみのコンパイル(灰色)は、最初はかなり遅いけれども最終的にはより良いピークに達し、第1層のみのコンパイルを上回ります。多層では両方の長所を兼ね備えており、最初も速く、より良いピークに到達します。

スループットのウォームアップカーブを下図で示します(これらのカーブはそれぞれ、前の図の対応するカーブの微分の逆数です)。

スループットのウォームアップカーブは、一般に経過時間のカーブよりも少しデコボコしていますが、これは各ポイントが現在の繰り返しの速度を表しており、それが変化する可能性があるためです。ここでは、最初の9回の繰り返しを拡大しています。多層モードでの曲線の凸凹の一部は、第1層でコンパイルされたコードの最適化が解除され、第2層でコンパイルされたコードに置き換えられたことによるものです。それにもかかわらず、特に初期の段階では、多層アプローチがより速くピークに達することがわかります。

Queuing policies

ウォームアップカーブの形状に影響を与えるもう一つの要因は、コンパイル要求の処理順序です。プログラムの初期には、インタープリタが大量のコールターゲットを実行し、コンパイラスレッドがこれらのリクエストに対応するよりも早く、コールターゲットがキューに押し込まれる傾向があります。このようにしてキューは大きくなっていきます。このような場合、コンパイラスレッドはスマートな方法でリクエストを選択しなければなりません。一般的には、コンパイラスレッドは、(インタープリタ時間のバランスからできるだけコールターゲットを早く取り除くため)将来インタープリタで最も多くの時間が費やされると予想されるコールターゲットを選択する必要があります。このことがなぜ重要なのか、2つの可能なキューイングのヒューリスティックを考えてみましょう。1つ目の方法では、スレッドは、インタープリタの時間が最も費やされるコールターゲットを推測します。2つ目のケースでは、コンパイラスレッドは常に最も重要でないコンパイル要求を選択します。明らかに、2番目のケースではインタープリタに多くの時間が費やされるため、対応する2つのウォームアップカーブは次のようになります。

最悪のポリシーは、最初はわずかにスループットを向上させるだけの ウォームアップカーブにつながり、最も重要なコールターゲットは最後にしか処理されないことになります。また、望ましくない副作用として、「最悪」のポリシーがリクエストキューをゆっくりと処理している間に、重要度の低いコールターゲットがどんどん閾値に達し、キューに押し込まれ、有用なコールターゲットのコンパイルを延期することで、さらなるスローダウンを引き起こすことがあります。この極端な例が、ほとんどの時間が費やされるコールターゲットを最初にコンパイルすることが重要である理由を示しています。他のすべての要因を無視して、効果的なキューイングポリシーにより、ウォームアップカーブをより凹状にすることができます。

キューイングポリシーは、ウォームアップカーブの「形状」を調整します。ピークポイントを左または右に移動させることができます。

多層コンパイルに関する作業の一環として、コンパイルキューイングにいくつかの変更を加えました。まず、コンパイルリクエスト到着順に処理されるのではなく、第2層のコンパイルリクエストよりも第1層のコンパイルリクエストを優先して処理します。さらに、リクエストは、コールカウントとコールカウント成長率に応じて、それぞれの階層内で優先度をつけます。ホットなコードほど早くコンパイルされるべきなので、たとえ何かが後でコンパイルキューに到着したとしても、すぐにコンパイルされるかもしれません。

もう一つの便利な機能は、コンパイルのしきい値の動的な調整です。実験的な機能として、コンパイルリクエストキューのサイズに応じて、コンパイル閾値を自動的に調整できます。

しかし、コンパイルキューについては非常に複雑なトピックなので、詳しくは別のブログ記事でご紹介します。

Community impact

ウォームアップは、特にUIアプリケーションを実行しているときに顕著であるため、私たちはGraalVMプロジェクトの活発な貢献者であるFabio Niephaus氏に、新しい多層コンパイラ・オプションに関するフィードバックを求めました。

Fabio Niephaus
https://twitter.com/fniephaus
TruffleSqueak
https://github.com/hpi-swa/trufflesqueak

TruffleSqueakの開発者である彼は、次のように述べています。


Graalコンパイラは、JVM モードで素晴らしいピーク性能を発揮しますが、アプリケーションによってはウォームアップに時間がかかる場合があります。TruffleSqueakはGraalVM言語として実装されたSmalltalkベースのIDEであるため、TruffleSqueakではこの現象を直接体験することができます。ウォームアップが不十分だと、TruffleSqueak のさまざまな部分が初めて実行されるときに、ユーザーインターフェース内で顕著につっかえたりラグが発生したりします。MPLR’19 の論文では、2 つの UI ベンチマークを使用してこの影響を記録しました(図 5 参照)。

GraalSqueak — Toward a Smalltalk-Based Tooling Platform for Polyglot Programming
https://fniephaus.com/2019/mplr19-graalsqueak.pdf

TruffleSqueakは、GraalVMの最新リリースに対応しており、GraalVMチームがGraalVM言語の多層コンパイル機能に取り組んでいることを知り、興奮しました。フィードバックを提供するために、私たちは論文に掲載されたBouncingAtomsMorph UIベンチマークを再実行しました。このベンチマークは、Smalltalk UIフレームワークを温めるためのシンプルなガスタンクシミュレーションです。以下がその様子を示すスクリーンショットです。

そして以下が結果です。

このグラフは、用意された同じベンチマーク環境で、新しい多層コンパイルオプションを使用した場合と使用しない場合の2回の実行を示しています(オレンジと青)。濃い色の2本の線は,時間経過に伴うフレームレートの変化を表しており(左のY軸)、明るい2本の線は対応するコンパイルキューのサイズを表しています(右のY軸)。

階層化コンパイルを有効にすると,シミュレーションを実行している環境のフレームレートの上昇が顕著になり,コンパイルキューの空になるのも速くなります.予想されるトレードオフとして、ピークパフォーマンスに到達するまでの時間が少し長くなります。興味深いことに、フレームレートは約90秒後にピークパフォーマンスに非常に近づき、その後、多層コンパイルオプションなしで実行したときのレートよりもわずかに低下します。同時に、コンパイラのキューは新しいコンパイルタスクでいっぱいになります。コンパイラのトレースによると、これは実際には Tier 2コンパイルが起動し、Tier 1 でコンパイルされたメソッドを置き換えているためです。これは、90秒前後でフレームレートが低下することの説明にもなります。簡単に説明すると、このグラフは2回の実行のデータを示しているだけなので、数値は必ずしも統計的に代表的なものではないことに注意してください。この2つの実験以外にもいろいろと試してみましたが、全体的な結果は変わりませんでした。ベンチマーク結果を再現したい場合は、次のGraalVM 21.1対応のTruffleSqueakリリースのTruffleSqueakUtilitiesのsetUpUIBenchmarkを評価できます。

これまでしばらくの間、多層オプションを使用してきて、一般的にコンパイルキューの負荷が軽減されると感じています。さらに重要なのは、TruffleSqueak のユーザーインターフェースがよりスムーズになりました。


Acknowledgements

この記事で説明した多層コンパイルとウォームアップの改善は、GraalVMチームの多くの人たちによる共同作業でした。多層コンパイルモードは、最初にAleksandar Prokopecが実装し、その後Boris Spasojevićが大幅にチューニングしましたが、彼は新しいキューイングポリシーの導入とチューニングも行いました。Peter Hofer、Christian Humer、Roland Schatz、Josef Eislなど、多くの人がこのGraalVMの機能に貢献しました。GraalVMのインタープリタは、Andreas Wöss、Daniele Bonetta、Benoit Daloze、Alfonso² Peterssenをはじめとする多くの方々によって改良されました。

Christian Wirthに感謝いたします。

コメントを残す

以下に詳細を記入するか、アイコンをクリックしてログインしてください。

WordPress.com ロゴ

WordPress.com アカウントを使ってコメントしています。 ログアウト /  変更 )

Twitter 画像

Twitter アカウントを使ってコメントしています。 ログアウト /  変更 )

Facebook の写真

Facebook アカウントを使ってコメントしています。 ログアウト /  変更 )

%s と連携中