Truffle 分支插桩

在基于 Truffle 实现的语言中,AST 实现通常包含快速和慢速执行路径,这通常基于某种条件(例如剖析)。这些执行路径被组织成不同的条件分支。在这种情况下,了解程序运行是否确实遍历了这些执行路径中的每一条代码通常很有帮助。

分支插桩功能对目标方法中的 if 语句进行插桩,以跟踪执行期间遍历了哪些分支。分支插桩通过用写入全局表的代码对分支进行插桩来实现这一点。每个分支在该表中都有一个条目。程序结束时,表中的内容将被解码并以可读形式转储到标准输出。

有几个标志控制分支插桩的工作方式。这些标志指定为系统属性:

  • --compiler.InstrumentBranches - 控制插桩是否开启(truefalse,默认为 false
  • --compiler.InstrumentFilter - 过滤应进行插桩的方法(方法过滤器语法,本质上是 <package>.<class>.<method>[.<signature>]
  • --compiler.InstrumentationTableSize - 控制插桩位置的最大数量
  • --compiler.InstrumentBranchesPerInlineSite - 控制插桩是否为每个客户语言函数/编译单元提供单独的分支剖析(默认为 false)。

使用示例 #

以下是如何在程序上启用分支插桩的示例。

在 Truffle 语言实现中使用插桩来检测热点或不常用分支时,通常首先找到带有问题方法的语言节点。以下命令运行 SimpleLanguage 的单元测试,并对所有 if 语句进行插桩:

mx --jdk jvmci sl --engine.BackgroundCompilation=false \
  --compiler.InstrumentBranches \
  '--compiler.InstrumentFilter=*.*.*' \
  ../truffle/truffle/com.oracle.truffle.sl.test/src/tests/LoopObjectDyn.sl

您将获得以下输出:

Execution profile (sorted by hotness)
=====================================
  0: *****************************************************
  1: **************************

com.oracle.truffle.sl.nodes.access.SLPropertyCacheNode.namesEqual(SLPropertyCacheNode.java:109) [bci: 120]
[0] state = IF(if=36054#, else=0#)

com.oracle.truffle.sl.nodes.controlflow.SLWhileRepeatingNode.executeRepeating(SLWhileRepeatingNode.java:102) [bci: 5]
[1] state = BOTH(if=18000#, else=18#)

此输出表明,在 SLWhileRepeatingNode.java 文件的第 102 行的 if 语句中,两个分支都已被访问;而在 SLPropertyCacheNode.java 文件的第 109 行的 if 语句中,只有 true 分支被访问。然而,它没有说明例如这个特定的 SLPropertyCacheNode 节点是从哪里使用的——相同的 execute 方法可以从许多不同的 SimpleLanguage 节点调用,您可能希望区分这些出现。因此,将 per-inline-site 标志设置为 true,并将过滤器更改为仅关注 SLPropertyCacheNode

mx --jdk jvmci sl -Djdk.graal.TruffleBackgroundCompilation=false \
  --compiler.InstrumentBranchesPerInlineSite \
  --compiler.InstrumentBranches \
  '--compiler.InstrumentFilter=*.SLPropertyCacheNode.*' \
  ../truffle/truffle/com.oracle.truffle.sl.test/src/tests/LoopObjectDyn.sl

这次您会得到更多输出,因为 namesEqual 方法在多个位置被内联(每个位置由其内联链表示)。以下输出片段首先显示了带有 if 语句 ID 及其出现次数的直方图。然后显示了分支的精确调用栈和执行次数。例如,对于 [1],当 namesEqualexecuteRead 调用时,true 分支被执行 18018 次。当 namesEqualexecuteWrite ([0]) 调用时,true 分支仅被执行 18 次:

Execution profile (sorted by hotness)
=====================================
  1: ***************************************
  2: ***************************************
  0:
  3:

com.oracle.truffle.sl.nodes.access.SLPropertyCacheNode.namesEqual(SLPropertyCacheNode.java:109) [bci: 120]
com.oracle.truffle.sl.nodes.access.SLReadPropertyCacheNodeGen.executeRead(SLReadPropertyCacheNodeGen.java:76) [bci: 88]
com.oracle.truffle.sl.nodes.access.SLReadPropertyNode.read(SLReadPropertyNode.java:71) [bci: 7]
com.oracle.truffle.sl.nodes.access.SLReadPropertyNodeGen.executeGeneric(SLReadPropertyNodeGen.java:30) [bci: 35]
com.oracle.truffle.sl.nodes.SLExpressionNode.executeLong(SLExpressionNode.java:81) [bci: 2]
com.oracle.truffle.sl.nodes.expression.SLLessThanNodeGen.executeBoolean_long_long0(SLLessThanNodeGen.java:42) [bci: 5]
com.oracle.truffle.sl.nodes.expression.SLLessThanNodeGen.executeBoolean(SLLessThanNodeGen.java:33) [bci: 14]
com.oracle.truffle.sl.nodes.controlflow.SLWhileRepeatingNode.evaluateCondition(SLWhileRepeatingNode.java:133) [bci: 5]
com.oracle.truffle.sl.nodes.controlflow.SLWhileRepeatingNode.executeRepeating(SLWhileRepeatingNode.java:102) [bci: 2]
com.oracle.truffle.runtime.OptimizedOSRLoopNode.executeLoop(OptimizedOSRLoopNode.java:113) [bci: 61]
com.oracle.truffle.sl.nodes.controlflow.SLWhileNode.executeVoid(SLWhileNode.java:69) [bci: 5]
com.oracle.truffle.sl.nodes.controlflow.SLBlockNode.executeVoid(SLBlockNode.java:84) [bci: 37]
com.oracle.truffle.sl.nodes.controlflow.SLFunctionBodyNode.executeGeneric(SLFunctionBodyNode.java:81) [bci: 5]
com.oracle.truffle.sl.nodes.SLRootNode.execute(SLRootNode.java:78) [bci: 28]
[1] state = IF(if=18018#, else=0#)

...

com.oracle.truffle.sl.nodes.access.SLPropertyCacheNode.namesEqual(SLPropertyCacheNode.java:109) [bci: 120]
com.oracle.truffle.sl.nodes.access.SLWritePropertyCacheNodeGen.executeWrite(SLWritePropertyCacheNodeGen.java:111) [bci: 244]
com.oracle.truffle.sl.nodes.access.SLWritePropertyNode.write(SLWritePropertyNode.java:73) [bci: 9]
com.oracle.truffle.sl.nodes.access.SLWritePropertyNodeGen.executeGeneric(SLWritePropertyNodeGen.java:33) [bci: 47]
com.oracle.truffle.sl.nodes.access.SLWritePropertyNodeGen.executeVoid(SLWritePropertyNodeGen.java:41) [bci: 2]
com.oracle.truffle.sl.nodes.controlflow.SLBlockNode.executeVoid(SLBlockNode.java:84) [bci: 37]
com.oracle.truffle.sl.nodes.controlflow.SLFunctionBodyNode.executeGeneric(SLFunctionBodyNode.java:81) [bci: 5]
com.oracle.truffle.sl.nodes.SLRootNode.execute(SLRootNode.java:78) [bci: 28]
[0] state = IF(if=18#, else=0#)

...

Truffle 调用边界插桩 #

Truffle 调用边界插桩工具对带有 TruffleCallBoundary 注解的方法的调用点进行插桩,并统计对这些方法的调用次数。它由以下一组标志控制:

  • --compiler.InstrumentBoundaries - 控制插桩是否开启(truefalse,默认为 false
  • --compiler.InstrumentFilter - 过滤应进行插桩的方法(方法过滤器语法,本质上是 <package>.<class>.<method>[.<signature>]
  • --compiler.InstrumentationTableSize - 控制插桩位置的最大数量
  • --compiler.InstrumentBoundariesPerInlineSite - 控制插桩是根据 Truffle 边界调用的声明进行(false),还是根据该调用点被内联的每个调用栈进行(true

此工具可以与分支插桩工具一起使用。

假设您需要找到频繁出现但未被内联(例如)的方法。识别 Truffle 调用边界的通常步骤是首先将 InstrumentBoundariesPerInlineSite 标志设置为 false 运行程序,然后,在识别出有问题的方法后,将该标志设置为 true 并设置 InstrumentFilter 以识别这些方法的特定调用栈。

联系我们