Truffle 分支检测

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

分支检测功能会检测目标方法中的 `if` 语句,以跟踪执行期间哪些分支已被使用。分支检测通过检测分支并使用写入全局表的代码来实现此目的。每个分支在该表中都有一个条目。当程序结束时,表的內容被解码并以可读的形式转储到标准输出。

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

  • --compiler.InstrumentBranches - 控制是否开启检测(`true` 或 `false`,默认值为 `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 节点调用,并且您可能希望区分这些情况。因此,将每个内联站点标志设置为 `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]`,当从 `executeRead` 调用 `namesEqual` 时,`true` 分支被执行了 `18018` 次。当从 `executeWrite`(`[0]`)调用 `namesEqual` 时,`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 - 控制是否开启检测(`true` 或 `false`,默认值为 `false`)
  • --compiler.InstrumentFilter - 过滤应该进行检测的方法(方法过滤器语法,本质上是 `<package>.<class>.<method>[.<signature>]`)
  • --compiler.InstrumentationTableSize - 控制检测位置的最大数量
  • --compiler.InstrumentBoundariesPerInlineSite - 控制检测是针对 Truffle 边界调用的声明(`false`)进行,还是针对该调用点被内联的每个调用堆栈(`true`)进行

此工具可以与分支检测工具一起使用。

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

联系我们