単相化のユースケース
このガイドでは、例を使用して、単相化によって動的な言語のパフォーマンスを向上させる方法を説明していますが、単相化の実装方法(分割ガイドを参照)や言語実装での単相化の活用方法(多相性のレポート・ガイドを参照)の詳細は説明していません。
単相化
単相化の利点をわかりやすく説明するために、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
から1回および文字列引数が指定されたcallsAddから1回コールされます。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++;
}
この例では、各型プロファイルが関数(addInt
およびaddString
)の個別のコピーに含まれるように、add
が複製(分割)されています。その結果、コンパイル時に、関数ごとに単一の型プロファイルのみが使用可能になり、コンパイルされたコードで負荷がかかる可能性のある型チェックが回避されます。
実行時に実行される適切な候補の検出とその複製の自動化は、単相化と呼ばれます。つまり、これはAST複製を介した多相ノードの実行時自動単相化です。
例1 - 引数の単相化
この例は、前の項で示した例を拡張したものです。add
関数は、引き続き単相化のターゲットであり、3セットの異なる引数(数値、文字列および配列)が指定されたaction
関数から3回コールされます。ウォームアップに十分な時間を確保するためにaction
関数を15秒間実行し、その後60秒間実行して各実行にかかった時間を追跡し、最後に平均をレポートします。このコードを2回実行します。1回は単相化を有効にし、もう1回は有効にせずに実行し、これら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個のランダムな倍精度値の配列を定義し、(action
関数で) 4つの異なるソート・メソッドを使用してこれを4回ソートします。最後に、前の例と同様に、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%です。