Truffle 語言安全點教學

從 21.1 版開始,Truffle 支援客座語言安全點。Truffle 安全點允許中斷客座語言的執行,以執行由語言或工具提交的執行緒本機動作。安全點是客座語言執行期間狀態一致且其他操作可以讀取其狀態的位置。

這取代了先前基於檢測或假設的安全點方法,這些方法需要使程式碼失效才能執行執行緒本機動作。新的實作使用快速執行緒本機檢查和被呼叫者暫存器儲存的存根呼叫,以優化效能並保持最小的額外負荷。這表示對於每個迴圈回邊和方法結束,我們都會執行額外的非揮發性讀取,這可能會導致稍微的效能下降。

使用案例 #

Truffle 語言安全點的常見使用案例包括

  • 在客座語言執行期間取消、請求結束或中斷。堆疊透過提交執行緒本機動作來解開。
  • 讀取目前執行緒以外的其他執行緒的目前堆疊追蹤資訊。
  • 列舉堆疊上所有作用中的物件參考。
  • 在給定的執行緒上執行客座訊號處理常式或客座 finalizer。
  • 實作將安全點機制作為其開發工具組一部分公開的客座語言。
  • 偵錯工具評估不支援在多個執行緒上執行的語言中的表達式。

語言支援 #

安全點會透過調用 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。不幸的是,並非總是能夠協同輪詢安全點,例如,如果程式碼目前執行協力廠商原生程式碼。未來的改進將允許在其他執行緒被封鎖時執行其他執行緒的程式碼。這就是建議使用 ThreadLocalAction.Access.getThread() 而不是直接使用 Thread.currentThread() 的原因之一。當原生呼叫傳回時,它需要等待目前為此執行緒執行的任何執行緒本機動作。這將能夠在其他執行緒被不協同的原生程式碼封鎖時收集其他執行緒的客座語言堆疊追蹤。目前,該動作將在原生程式碼傳回時的下一個安全點位置執行。

用於偵錯的工具 #

有多個可用的偵錯選項

使用 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 毫秒。請注意,精確計時可能會取決於 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 和 Hanspeter Mössenböck。「客座語言安全點的技術和應用」。在第 10 屆物件導向語言、程式和系統實作、編譯、優化研討會的論文集中,第 1-10 頁。2015 年。

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

與我們聯繫