- 适用于 JDK 24 的 GraalVM(最新)
- 适用于 JDK 25 的 GraalVM(早期访问)
- 适用于 JDK 21 的 GraalVM
- 适用于 JDK 17 的 GraalVM
- 存档
- 开发构建
- Truffle 语言实现框架
- Truffle 分支插桩
- 动态对象模型
- 静态对象模型
- 解释器代码的主机优化
- Truffle 函数内联方法
- 分析 Truffle 解释器
- Truffle 互操作 2.0
- 语言实现
- 使用 Truffle 实现新语言
- Truffle 语言和工具迁移到 Java 模块
- Truffle 原生函数接口
- 优化 Truffle 解释器
- 选项
- 栈上替换
- Truffle 字符串指南
- 特化直方图
- 测试 DSL 特化
- 基于多语言 API 的 TCK
- Truffle 编译队列方法
- Truffle 库指南
- Truffle AOT 概述
- Truffle AOT 编译
- 辅助引擎缓存
- Truffle 语言安全点教程
- 单态化
- 拆分算法
- 单态化用例
- 向运行时报告多态特化
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 节点调用,您可能希望区分这些出现。因此,将 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]
,当 namesEqual
从 executeRead
调用时,true
分支被执行 18018
次。当 namesEqual
从 executeWrite
([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
- 控制插桩是否开启(true
或false
,默认为false
)--compiler.InstrumentFilter
- 过滤应进行插桩的方法(方法过滤器语法,本质上是<package>.<class>.<method>[.<signature>]
)--compiler.InstrumentationTableSize
- 控制插桩位置的最大数量--compiler.InstrumentBoundariesPerInlineSite
- 控制插桩是根据 Truffle 边界调用的声明进行(false
),还是根据该调用点被内联的每个调用栈进行(true
)
此工具可以与分支插桩工具一起使用。
假设您需要找到频繁出现但未被内联(例如)的方法。识别 Truffle 调用边界的通常步骤是首先将 InstrumentBoundariesPerInlineSite
标志设置为 false
运行程序,然后,在识别出有问题的方法后,将该标志设置为 true
并设置 InstrumentFilter
以识别这些方法的特定调用栈。