- 適用於 JDK 23 的 GraalVM (最新)
- 適用於 JDK 24 的 GraalVM (搶先體驗)
- 適用於 JDK 21 的 GraalVM
- 適用於 JDK 17 的 GraalVM
- 封存
- 開發版本
- Truffle 語言實作框架
- Truffle 分支檢測
- 動態物件模型
- 靜態物件模型
- 解譯器程式碼的主機最佳化
- Truffle 函式內聯方法
- 分析 Truffle 解譯器
- Truffle Interop 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%。