Truffle言語セーフポイントのチュートリアル

21.1以降、Truffleはゲスト言語のセーフポイントをサポートしています。Truffleセーフポイントを使用すると、ゲスト言語の実行を中断して、言語またはツールによって送信されたスレッド・ローカル・アクションを実行できます。セーフポイントとは、ゲスト言語の実行中に、状態の一貫性が保たれ、他の操作がその状態を読み取ることができる場所です。

これは、以前のインストゥルメンテーションまたは仮定ベースのセーフポイントへのアプローチに置き換わるものです。以前は、スレッド・ローカル・アクションを実行するためにコードを無効にする必要がありました。新しい実装では高速なスレッド・ローカル・チェックが使用され、コール先は保存されたスタブ・コールを登録してパフォーマンスを最適化し、オーバーヘッドを最小限に抑えます。つまり、すべてのループ・バックエッジおよびメソッド・イグジットに対して追加の不揮発性読取りを実行します。これにより、わずかなスローダウンが発生する可能性があります。

ユース・ケース

Truffle言語セーフポイントの一般的なユース・ケースは次のとおりです:

言語サポート

セーフポイントは、TruffleSafepoint.poll(Node)メソッドを呼び出すことで明示的にポーリングされます。Truffleゲスト言語の実装では、一定の時間間隔内でセーフポイントが繰り返しポーリングされるようにする必要があります。たとえば、単一の算術式は一定数のCPUサイクル内で完了するようにします。ただし、配列の値を要約するループでは、実際の配列サイズに依存する、不定の時間が使用されます。これは通常、ループの最後と、再帰をカバーするための関数コールまたはメソッド・コールの最後に、セーフポイントのポーリングが最適に行われることを意味します。また、実行をブロックするゲスト言語コードは、ゲスト言語のロックと同様に、TruffleSafepoint.setBlocked(Interrupter) APIを使用して、スレッドの待機中にセーフポイントの協調的ポーリングを許可する必要があります。

javadocで、スレッド・ローカル・アクションをサポートするために言語実装で実行する必要があるステップの詳細を参照してください。

スレッド・ローカル・アクション

言語およびインストゥルメントは、その環境を使用してアクションを送信できます。

使用例:


Env env; // language or instrument environment

env.submitThreadLocal(null, new ThreadLocalAction(true /*side-effecting*/, true /*synchronous*/) {
     @Override
     protected void perform(Access access) {
         assert access.getThread() == Thread.currentThread();
     }
});

詳細は、javadocを参照してください。

現在の制限事項

スレッドが境界注釈付きメソッドで実行されている間は、メソッドがセーフポイントを協調的にポーリングするか、ブロッキングAPIを使用しないかぎり、スレッド・ローカル・アクションを実行する方法は現時点ではありません。コードが現在サード・パーティのネイティブ・コードを実行している場合など、いつでもセーフポイントを協調的にポーリングできるというわけではありません。将来の改善により、ブロックされている間も他のスレッドに対してコードを実行できるようになる予定です。これが、Thread.currentThread()を直接使用するかわりにThreadLocalAction.Access.getThread()を使用することをお薦めする理由の1つです。ネイティブ・コールが戻ったとき、このスレッドに対して現在実行されているスレッド・ローカル・アクションを待機する必要があります。これにより、非協調的なネイティブ・コードによってブロックされている間に、他のスレッドからゲスト言語スタック・トレースを収集できるようになります。現時点では、ネイティブ・コードが戻ったときに、アクションは次のセーフポイントの場所で実行されます。

デバッグ用のツール

いくつかのデバッグ・オプションがあります:

SafepointALotを使用したセーフポイントの設定

SafepointALotは、アプリケーションのすべてのセーフポイントを設定し、統計を収集するためのツールです。

--engine.SafepointALotオプションを指定してこれを有効にすると、実行終了時のセーフポイント間のCPU時間間隔に関する統計が出力されます。

たとえば、次を実行するとします:

graalvm/bin/js --engine.SafepointALot js-benchmarks/harness.js -- octane-deltablue.js

コンテキストのクローズ時に次の出力がログに記録されます:

DeltaBlue: 540
[engine] Safepoint Statistics
  --------------------------------------------------------------------------------------
   Thread Name         Safepoints | Interval     Avg              Min              Max
  --------------------------------------------------------------------------------------
   main                  48384054 |            0.425 us           0.1 us       44281.1 us
  -------------------------------------------------------------------------------------
   All threads           48384054 |            0.425 us           0.1 us       42281.1 us

ゲスト言語の実装では、平均で1ミリ秒未満に保つことをお薦めします。正確なタイミングは、GCによるCPUおよび中断に依存する可能性があります。GC時間はセーフポイント間隔時間に含まれるため、最大値は最大GC中断時間に近くなることが予想されます。このツールの将来のバージョンでは、この統計からGC中断時間を除外できるようになります。

スレッド・ローカル・アクションのトレース

オプション--engine.TraceThreadLocalActionsを使用すると、任意の起点のすべてのスレッド・ローカル・アクションをトレースできます。

出力例:

[engine] [tl] submit                 0  thread[main]                action[SampleAction$8@5672f0d1]     all-threads[alive=4]        side-effecting     asynchronous
[engine] [tl]   perform-start        0  thread[pool-1-thread-410]   action[SampleAction$8@5672f0d1]
[engine] [tl]   perform-start        0  thread[pool-1-thread-413]   action[SampleAction$8@5672f0d1]
[engine] [tl]   perform-start        0  thread[pool-1-thread-412]   action[SampleAction$8@5672f0d1]
[engine] [tl]   perform-done         0  thread[pool-1-thread-413]   action[SampleAction$8@5672f0d1]
[engine] [tl]   perform-done         0  thread[pool-1-thread-410]   action[SampleAction$8@5672f0d1]
[engine] [tl]   perform-start        0  thread[pool-1-thread-411]   action[SampleAction$8@5672f0d1]
[engine] [tl]   perform-done         0  thread[pool-1-thread-412]   action[SampleAction$8@5672f0d1]
[engine] [tl]   perform-done         0  thread[pool-1-thread-411]   action[SampleAction$8@5672f0d1]
[engine] [tl] done                   0  thread[pool-1-thread-411]   action[SampleAction$8@5672f0d1]

時間間隔ごとのゲストおよびホスト・スタック・フレームの出力。

オプション--engine.TraceStackTraceInterval=1000を使用すると、現在のスタック・トレースを繰り返し出力する時間間隔をミリ秒単位で設定できます。スタック・トレースは次のセーフポイント・ポーリングで出力されるため、正確でない可能性があります。

graalvm/bin/js --engine.TraceStackTraceInterval=1000 js-benchmarks/harness.js -- octane-deltablue.js

次のように出力されます:

[engine] Stack Trace Thread main: org.graalvm.polyglot.PolyglotException
	at <js> BinaryConstraint.chooseMethod(octane-deltablue.js:359-381:9802-10557)
	at <js> Constraint.satisfy(octane-deltablue.js:176:5253-5275)
	at <js> Planner.incrementalAdd(octane-deltablue.js:597:16779-16802)
	at <js> Constraint.addConstraint(octane-deltablue.js:165:4883-4910)
	at <js> UnaryConstraint(octane-deltablue.js:219:6430-6449)
	at <js> StayConstraint(octane-deltablue.js:297:8382-8431)
	at <js> chainTest(octane-deltablue.js:817:23780-23828)
	at <js> deltaBlue(octane-deltablue.js:883:25703-25716)
	at <js> MeasureDefault(harness.js:552:20369-20383)
	at <js> BenchmarkSuite.RunSingleBenchmark(harness.js:614:22538-22550)
	at <js> RunNextBenchmark(harness.js:340:11560-11614)
	at <js> RunStep(harness.js:141:5673-5686)
	at <js> BenchmarkSuite.RunSuites(harness.js:160:6247-6255)
	at <js> runBenchmarks(harness.js:686-688:24861-25023)
	at <js> main(harness.js:734:26039-26085)
	at <js> :program(harness.js:783:27470-27484)
	at org.graalvm.polyglot.Context.eval(Context.java:348)
	at com.oracle.truffle.js.shell.JSLauncher.executeScripts(JSLauncher.java:347)
	at com.oracle.truffle.js.shell.JSLauncher.launch(JSLauncher.java:88)
	at org.graalvm.launcher.AbstractLanguageLauncher.launch(AbstractLanguageLauncher.java:124)
	at org.graalvm.launcher.AbstractLanguageLauncher.launch(AbstractLanguageLauncher.java:71)
	at com.oracle.truffle.js.shell.JSLauncher.main(JSLauncher.java:73)

[engine] Stack Trace Thread main: org.graalvm.polyglot.PolyglotException
	at <js> EqualityConstraint.execute(octane-deltablue.js:528-530:14772-14830)
	at <js> Plan.execute(octane-deltablue.js:781:22638-22648)
	at <js> chainTest(octane-deltablue.js:824:24064-24077)
	at <js> deltaBlue(octane-deltablue.js:883:25703-25716)
	at <js> MeasureDefault(harness.js:552:20369-20383)
	at <js> BenchmarkSuite.RunSingleBenchmark(harness.js:614:22538-22550)
	at <js> RunNextBenchmark(harness.js:340:11560-11614)
	at <js> RunStep(harness.js:141:5673-5686)
	at <js> BenchmarkSuite.RunSuites(harness.js:160:6247-6255)
	at <js> runBenchmarks(harness.js:686-688:24861-25023)
	at <js> main(harness.js:734:26039-26085)
	at <js> :program(harness.js:783:27470-27484)
	at org.graalvm.polyglot.Context.eval(Context.java:348)
	at com.oracle.truffle.js.shell.JSLauncher.executeScripts(JSLauncher.java:347)
	at com.oracle.truffle.js.shell.JSLauncher.launch(JSLauncher.java:88)
	at org.graalvm.launcher.AbstractLanguageLauncher.launch(AbstractLanguageLauncher.java:124)
	at org.graalvm.launcher.AbstractLanguageLauncher.launch(AbstractLanguageLauncher.java:71)
	at com.oracle.truffle.js.shell.JSLauncher.main(JSLauncher.java:73)

その他の情報

Daloze、Benoit、Chris Seaton、Daniele BonettaおよびHanspeter Mössenböck著。『Techniques and applications for guest-language safepoints』(『Proceedings of the 10th Workshop on Implementation, Compilation, Optimization of Object-Oriented Languages, Programs and Systems, pp. 1-10. 2015』より)

https://dl.acm.org/doi/abs/10.1145/2843915.2843921