1. ホーム
  2. javascript

[解決済み] v8 JavaScriptのconst、let、varのパフォーマンスへの影響?

2022-09-04 05:41:07

質問

機能の違いに関係なく、新しいキーワード 'let' と 'const' を使用すると、'var' と比較してパフォーマンスに何らかの一般的または特異的な影響がありますか?

プログラムを実行した後

function timeit(f, N, S) {
    var start, timeTaken;
    var stats = {min: 1e50, max: 0, N: 0, sum: 0, sqsum: 0};
    var i;
    for (i = 0; i < S; ++i) {
        start = Date.now();
        f(N);
        timeTaken = Date.now() - start;

        stats.min = Math.min(timeTaken, stats.min);
        stats.max = Math.max(timeTaken, stats.max);
        stats.sum += timeTaken;
        stats.sqsum += timeTaken * timeTaken;
        stats.N++
    }

    var mean = stats.sum / stats.N;
    var sqmean = stats.sqsum / stats.N;

    return {min: stats.min, max: stats.max, mean: mean, spread: Math.sqrt(sqmean - mean * mean)};
}

var variable1 = 10;
var variable2 = 10;
var variable3 = 10;
var variable4 = 10;
var variable5 = 10;
var variable6 = 10;
var variable7 = 10;
var variable8 = 10;
var variable9 = 10;
var variable10 = 10;

function varAccess(N) {
    var i, sum;
    for (i = 0; i < N; ++i) {
        sum += variable1;
        sum += variable2;
        sum += variable3;
        sum += variable4;
        sum += variable5;
        sum += variable6;
        sum += variable7;
        sum += variable8;
        sum += variable9;
        sum += variable10;
    }
    return sum;
}

const constant1 = 10;
const constant2 = 10;
const constant3 = 10;
const constant4 = 10;
const constant5 = 10;
const constant6 = 10;
const constant7 = 10;
const constant8 = 10;
const constant9 = 10;
const constant10 = 10;

function constAccess(N) {
    var i, sum;
    for (i = 0; i < N; ++i) {
        sum += constant1;
        sum += constant2;
        sum += constant3;
        sum += constant4;
        sum += constant5;
        sum += constant6;
        sum += constant7;
        sum += constant8;
        sum += constant9;
        sum += constant10;
    }
    return sum;
}


function control(N) {
    var i, sum;
    for (i = 0; i < N; ++i) {
        sum += 10;
        sum += 10;
        sum += 10;
        sum += 10;
        sum += 10;
        sum += 10;
        sum += 10;
        sum += 10;
        sum += 10;
        sum += 10;
    }
    return sum;
}

console.log("ctl = " + JSON.stringify(timeit(control, 10000000, 50)));
console.log("con = " + JSON.stringify(timeit(constAccess, 10000000, 50)));
console.log("var = " + JSON.stringify(timeit(varAccess, 10000000, 50)));

.. 私の結果は以下の通りでした。

ctl = {"min":101,"max":117,"mean":108.34,"spread":4.145407097016924}
con = {"min":107,"max":572,"mean":435.7,"spread":169.4998820058587}
var = {"min":103,"max":608,"mean":439.82,"spread":176.44417700791374}

しかし、ここで述べたような議論は、特定のシナリオにおけるパフォーマンスの違いの実際の可能性を示しているようです。 https://esdiscuss.org/topic/performance-concern-with-let-const

どのように解決するのですか?

TL;DR

理論的には というように、このループの最適化されていないバージョンです。

for (let i = 0; i < 500; ++i) {
    doSomethingWith(i);
}

を使った最適化されていないバージョンのループより遅いかもしれません。 var :

for (var i = 0; i < 500; ++i) {
    doSomethingWith(i);
}

というのは 異なる i 変数は、ループの繰り返しごとに let であるのに対し ivar .

それに対しての反論 というのは var はループの外側で宣言されているのに対して let はループの中でしか宣言されないのに対して、はループの外で宣言されるので、最適化の利点があるかもしれません。

実際には 2018年現在、最新のJavaScriptエンジンはループのイントロスペクションを十分に行い、いつその差を最適化できるかを知っています。(それ以前でも、あなたのループは十分な仕事をしていて、追加の let -に関連するオーバーヘッドが消えてしまうほど、ループは十分に機能していました。しかし、今ではそれを心配する必要さえありません)。

合成ベンチマークに注意 これらは非常に間違えやすく、実際のコードにはない方法で JavaScript エンジンの最適化機能を作動させます (良い意味でも悪い意味でも)。しかし、合成ベンチマークが必要な場合は、これを使用します。

const now = typeof performance === "object" && performance.now
    ? performance.now.bind(performance)
    : Date.now.bind(Date);

const btn = document.getElementById("btn");
btn.addEventListener("click", function() {
    btn.disabled = true;
    runTest();
});

const maxTests = 100;
const loopLimit = 50000000;
const expectedX = 1249999975000000;

function runTest(index = 1, results = {usingVar: 0, usingLet: 0}) {
    console.log(`Running Test #${index} of ${maxTests}`);
    setTimeout(() => {
        const varTime = usingVar();
        const letTime = usingLet();
        results.usingVar += varTime;
        results.usingLet += letTime;
        console.log(`Test ${index}: var = ${varTime}ms, let = ${letTime}ms`);
        ++index;
        if (index <= maxTests) {
            setTimeout(() => runTest(index, results), 0);
        } else {
            console.log(`Average time with var: ${(results.usingVar / maxTests).toFixed(2)}ms`);
            console.log(`Average time with let: ${(results.usingLet / maxTests).toFixed(2)}ms`);
            btn.disabled = false;
        }
    }, 0);
}

function usingVar() {
    const start = now();
    let x = 0;
    for (var i = 0; i < loopLimit; i++) {
        x += i;
    }
    if (x !== expectedX) {
        throw new Error("Error in test");
    }
    return now() - start;
}

function usingLet() {
    const start = now();
    let x = 0;
    for (let i = 0; i < loopLimit; i++) {
        x += i;
    }
    if (x !== expectedX) {
        throw new Error("Error in test");
    }
    return now() - start;
}
<input id="btn" type="button" value="Start">

V8/Chrome または SpiderMonkey/Firefox のいずれでも、その合成テストに有意差はないと書かれています。(両方のブラウザでテストを繰り返すと、一方が勝つか、もう一方が勝つか、どちらの場合も誤差の範囲になります)。しかし、繰り返しますが、これは合成ベンチマークであり、あなたのコードではありません。あなたのコードがパフォーマンスの問題を抱えたとき、そして抱えた場合に、コードのパフォーマンスについて心配するのです。

スタイルの問題として、私は let の方がスコープの利点と、ループ変数をクロージャで使う場合のクロージャ・イン・ループの利点があるからです。

詳細

との重要な違いは varlet の中に for のループは、別の i が作成されることです。これは古典的な「クロージャ・イン・ループ」問題に対処するものです。

function usingVar() {
  for (var i = 0; i < 3; ++i) {
    setTimeout(function() {
      console.log("var's i: " + i);
    }, 0);
  }
}
function usingLet() {
  for (let i = 0; i < 3; ++i) {
    setTimeout(function() {
      console.log("let's i: " + i);
    }, 0);
  }
}
usingVar();
setTimeout(usingLet, 20);

ループ本体ごとに新しいEnvironmentRecordを作成する( スペックリンク ) は作業であり、作業には時間がかかります。そのため、理論上は let バージョンよりも遅くなります。 var バージョンよりも遅くなります。

しかし、この違いが問題になるのは、ループ内で関数(クロージャ)を作成し、その関数で i を使用するループ内の関数 (クロージャ) を作成する場合のみです。そうでなければ、この違いは観察できず、最適化することができます。

2018 年現在、V8 (および Firefox の SpiderMonkey) は十分なイントロスペクションを行っているようで、 を使用しないループにパフォーマンス コストはありません。 let の可変反復セマンティクスを利用しないループではパフォーマンスコストがかからないという十分なイントロスペクションを行っているようです。参照 このテスト .


場合によっては const が最適化の機会を提供することがあります。 var ではできない最適化の機会を与えるかもしれません。特にグローバル変数ではそうです。

グローバル変数の問題は、それがまあ、グローバルであるということです。 任意の コード どこでも でアクセスすることができます。ですから、もしあなたが変数を var で変数を宣言した場合、エンジンはその変数が後で読み込まれたコードの結果、決して変更されないと仮定することができないのです。

const では、値が変更できないことをエンジンに明示的に伝えていることになります¹。そのため、値が変更できないことを知りながら、それを使用するコードに変数参照ではなくリテラルを出力するなど、エンジンが望む最適化を自由に行うことができます。

¹ オブジェクトの場合、値は 参照 への参照であり、オブジェクトそのものではないことに注意してください。そのため const o = {} であれば、オブジェクトの状態を変更することができます ( o.answer = 42 ) を作ることはできませんが o を新しいオブジェクトを指すようにすることはできません (そうすると、それが含むオブジェクトの参照を変更する必要があるからです)。


を使用する場合 let または const で、他の var -のような状況であっても、性能に違いが出ることはないでしょう。この関数は var を使っても let といった具合です。

function foo() {
    var i = 0;
    while (Math.random() < 0.5) {
        ++i;
    }
    return i;
}


もちろん、これらはすべて重要ではありませんし、解決すべき真の問題がある場合にのみ、心配することです。