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();
     }

    @Override
    protected void notifyBlocked(Access access) {
        assert access.getThread() == Thread.currentThread();
    }

    @Override
    protected void notifyUnblocked(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: 3037
[engine] Safepoint Statistics 
  ------------------------------------------------------------------------------------------------------------------------------------------------------- 
   Thread Name         Safepoints | Interval     Avg              Min              Max      | Blocked Intervals   Avg              Min              Max
  ------------------------------------------------------------------------------------------------------------------------------------------------------- 
   main                  83187332 |            0,452 us           0,2 us      104938,8 us   |      18            6,536 us           0,9 us          36,8 us
  ------------------------------------------------------------------------------------------------------------------------------------------------------- 
   All threads           83187332 |            0,452 us           0,2 us      104938,8 us   |      18            6,536 us           0,9 us          36,8 us

建议访客语言实现尝试将平均值保持在1毫秒以下。请注意,精确计时可能取决于CPU和GC(垃圾回收)中断。由于GC时间包含在安全点间隔时间内,因此预计最大值会接近GC最大中断时间。该工具的未来版本将能够从该统计数据中排除GC中断时间。

SafepointALot工具也不会中断通常等待某些资源可用(例如,IO,线程启动)的阻塞操作。这些阻塞间隔在统计数据中单独显示。普通的线程本地操作会中断阻塞操作,因此阻塞间隔不适用于它们。

查找缺失的安全点轮询 #

选项 `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

联系我们