- GraalVM for JDK 23 (最新)
- GraalVM for JDK 24 (早期アクセス)
- GraalVM for JDK 21
- GraalVM for JDK 17
- アーカイブ
- 開発ビルド
- 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回呼び出されます。ウォームアップのために15秒間`action`関数を実行し、その後60秒間実行して各実行にかかった時間を追跡し、最後に平均を報告します。このコードを単態化を有効にした場合と無効にした場合の両方で実行し、これらの2回のランの出力と高速化を報告します。
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`関数が定義されています。0から1の間の1000個のランダムな倍精度浮動小数点値の配列を定義し、4つの異なるソート方法(`action`関数内)を使用して4回ソートします。最後に、前の例と同様に、15秒間`action`関数をウォームアップし、単態化ありとなしの場合のこの関数の平均実行時間を次の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%です。