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

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

不足しているセーフポイントポーリングの検出 #

TraceMissingSafepointPollIntervalオプションは、不足しているセーフポイントポーリングの検出に役立ちます。次のように使用します。

$ bin/js --experimental-options --engine.TraceMissingSafepointPollInterval=20 -e 'print(6*7)'
...
42
[engine] No TruffleSafepoint.poll() for 36ms on main (stacktrace 1ms after the last poll)
	at java.base/java.lang.StringLatin1.replace(StringLatin1.java:312)
	at java.base/java.lang.String.replace(String.java:2933)
	at java.base/jdk.internal.loader.BuiltinClassLoader.defineClass(BuiltinClassLoader.java:801)
	at java.base/jdk.internal.loader.BuiltinClassLoader.findClassInModuleOrNull(BuiltinClassLoader.java:741)
	at java.base/jdk.internal.loader.BuiltinClassLoader.loadClassOrNull(BuiltinClassLoader.java:665)
	at java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(BuiltinClassLoader.java:639)
	at java.base/jdk.internal.loader.ClassLoaders$AppClassLoader.loadClass(ClassLoaders.java:188)
	at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:526)
	at org.graalvm.truffle/com.oracle.truffle.polyglot.PolyglotValueDispatch.createInteropValue(PolyglotValueDispatch.java:1694)
	at org.graalvm.truffle/com.oracle.truffle.polyglot.PolyglotLanguageInstance$1.apply(PolyglotLanguageInstance.java:149)
	at org.graalvm.truffle/com.oracle.truffle.polyglot.PolyglotLanguageInstance$1.apply(PolyglotLanguageInstance.java:147)
	at java.base/java.util.concurrent.ConcurrentHashMap.computeIfAbsent(ConcurrentHashMap.java:1708)
	at org.graalvm.truffle/com.oracle.truffle.polyglot.PolyglotLanguageInstance.lookupValueCacheImpl(PolyglotLanguageInstance.java:147)
	at org.graalvm.truffle/com.oracle.truffle.polyglot.PolyglotLanguageInstance.lookupValueCache(PolyglotLanguageInstance.java:137)
	at org.graalvm.truffle/com.oracle.truffle.polyglot.PolyglotLanguageContext.asValue(PolyglotLanguageContext.java:948)
	at org.graalvm.truffle/com.oracle.truffle.polyglot.PolyglotContextImpl.eval(PolyglotContextImpl.java:1686)
	at org.graalvm.truffle/com.oracle.truffle.polyglot.PolyglotContextDispatch.eval(PolyglotContextDispatch.java:60)
	at org.graalvm.polyglot/org.graalvm.polyglot.Context.eval(Context.java:402)
	at org.graalvm.js.launcher/com.oracle.truffle.js.shell.JSLauncher.executeScripts(JSLauncher.java:365)
	at org.graalvm.js.launcher/com.oracle.truffle.js.shell.JSLauncher.launch(JSLauncher.java:93)
	at org.graalvm.launcher/org.graalvm.launcher.AbstractLanguageLauncher.launch(AbstractLanguageLauncher.java:296)
	at org.graalvm.launcher/org.graalvm.launcher.AbstractLanguageLauncher.launch(AbstractLanguageLauncher.java:121)
	at org.graalvm.launcher/org.graalvm.launcher.AbstractLanguageLauncher.runLauncher(AbstractLanguageLauncher.java:168)
...

最後のNミリ秒間にセーフポイントポーリングがなかった場合、ホストスタックトレースを出力します。NはTraceMissingSafepointPollIntervalの引数です。

HotSpotでは、クラスローディングのため、ゲストセーフポイント間に長い遅延が発生する可能性があるため、ネイティブイメージで実行するか、クラスローディング以外のスタックトレースに焦点を当てるのが理にかなっています。

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

--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, and Hanspeter Mössenböck. “Techniques and applications for guest-language safepoints.” In 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

お問い合わせ