- 适用于 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 语言安全点教程
- 单态化
- 拆分算法
- 单态化用例
- 向运行时报告多态特化
单态化用例
本指南通过示例演示了单态化如何提高动态语言的性能,但并未深入探讨单态化的实现细节(在拆分指南中描述)或如何在您的语言实现中利用单态化(在报告多态性指南中描述)。
单态化 #
为了更好地说明单态化的好处,我们来看一个用 JavaScript 编写的小例子
function add(arg1, arg2) {
return arg1 + arg2;
}
function callsAdd() {
add(1, 2);
add("foo", "bar");
}
var i = 0;
while (i < 1000) {
callsAdd();
i++;
}
如您在此示例中所示,add
函数在 callsAdd
中被调用两次,一次使用整数参数,一次使用字符串参数。一旦 add
执行的次数足够多以触发编译,其执行配置文件将显示 +
运算符已与整数和字符串一起执行,因此将编译两种类型的处理程序(即类型检查和执行),这会对性能产生影响。通过如下重写示例可以避免这种情况:
function addInt(arg1, arg2) {
return arg1 + arg2;
}
function addString(arg1, arg2) {
return arg1 + arg2;
}
function callsAdd() {
addInt(1, 2);
addString("foo", "bar");
}
i = 0;
while (i < 1000) {
callsAdd();
i++;
}
在此示例中,add
已被复制(拆分),使得每个类型配置文件都包含在函数的一个单独副本中(addInt
和 addString
)。结果是,在编译时,每个函数只有一个类型配置文件可用,从而避免了编译代码中可能耗费性能的类型检查。
在运行时自动化检测合适的候选者以及它们的复制,就是我们所说的单态化。换句话说,它是通过 AST 复制对多态节点进行的自动化运行时单态化。
示例 1 - 参数的单态化 #
此示例是上一节中说明性示例的扩展版本。add
函数仍然是单态化的目标,并从 action
函数中被调用 3 次,使用 3 组不同的参数(数字、字符串和数组)。执行 action
函数 15 秒以预热足够的时间,然后执行 60 秒,记录每次执行所花费的时间,并最终报告平均值。此代码执行两次:一次启用单态化,一次不启用单态化,并报告这两次运行的输出以及加速比。
function add(arg1, arg2) {
return arg1 + arg2;
}
var global = 0;
function action() {
for (var i = 0; i < 10000; i++) {
global = add(1, 2);
global = add("foo", "bar");
global = add([1,2,3], [4,5,6]);
}
}
// Warm up.
var start = Date.now();
while ((Date.now() - start) < 15000 /* 15 seconds */) {
action();
}
// Benchmark
var iterations = 0;
var sum = 0;
var start = Date.now();
while ((Date.now() - start) < 60000 /* 60 seconds */) {
var thisIterationStart = Date.now();
action();
var thisIterationTime = Date.now() - thisIterationStart;
iterations++;
sum += thisIterationTime;
}
console.log(sum / iterations);
不启用单态化的输出是 4.494225288735564。启用单态化的输出是 4.2421633923。加速比约为 5%。
示例 2 - 间接调用的单态化 #
此示例稍微复杂一些,说明了单态化如何有益于高阶函数。在此示例中,定义了 insertionSort
函数,该函数给定一个项目数组和一个用于比较这些项目的函数,使用插入排序对数组进行排序。定义一个包含 1000 个介于 0 和 1 之间的随机双精度值的数组,并使用 4 种不同的排序方法(在 action
函数中)对其进行四次排序。最后,与上一个示例一样,预热 action
函数 15 秒,并报告在启用和不启用单态化的情况下,该函数在接下来的 60 秒内的平均执行时间。
function insertionSort (items, comparator) {
for (var i = 0; i < items.length; i++) {
let value = items[i];
for (var j = i - 1; j >= 0 && comparator(items[j], value); j--) {
items[j + 1] = items[j]
}
items[j + 1] = value
}
}
// Random values in an array
var array = new Array(1000);
for (i = 0; i < array.length; i++) {
array[i] = Math.random();
}
function action() {
insertionSort(array, function (a, b) { return a < b });
insertionSort(array, function (a, b) { return a > b });
insertionSort(array, function (a, b) { return a.toString().length < b.toString().length; });
insertionSort(array, function (a, b) { return a.toString().length > b.toString().length; });
}
// Warm up.
var start = Date.now();
while ((Date.now() - start) < 15000 /* 15 seconds */) {
action();
}
// Benchmark
var iterations = 0;
var sum = 0;
var start = Date.now();
while ((Date.now() - start) < 60000 /* 60 seconds */) {
var thisIterationStart = Date.now();
action();
var thisIterationTime = Date.now() - thisIterationStart;
iterations++;
sum += thisIterationTime;
}
console.log(sum / iterations);
不启用单态化的输出是 194.05161290322582。启用单态化的输出是 175.41071428571428。加速比约为 10%。