- GraalVM for JDK 23 (最新)
- GraalVM for JDK 24 (抢先体验版)
- GraalVM for JDK 21
- GraalVM for JDK 17
- 存档
- 开发版本
- Truffle 语言实现框架
- Truffle 分支检测
- 动态对象模型
- 静态对象模型
- 解释器代码的宿主优化
- Truffle 对函数内联的方法
- 分析 Truffle 解释器
- Truffle 交互 2.0
- 语言实现
- 使用 Truffle 实现新语言
- Truffle 语言和工具迁移到 Java 模块
- Truffle 本地函数接口
- 优化 Truffle 解释器
- 选项
- 栈上替换
- Truffle 字符串指南
- 专门化直方图
- 测试 DSL 专门化
- 基于 Polyglot 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%。