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。不幸的是,并非总是可以协作轮询安全点,例如,如果代码当前正在执行第三方本机代码。未来的改进将允许在线程被阻塞时为其他线程运行代码。这是建议使用 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

联系我们