const、let 和 var 对 v8 JavaScript 性能的影响?

2022-01-23 00:00:00 performance constants javascript v8 let

不管功能上的差异,使用新的关键字let"和const"是否会对性能产生任何一般性或特定性的影响?

Regardless of functional differences, does using the new keywords 'let' and 'const' have any generalized or specific impact on performance relative to '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

However discussion as noted here seems to indicate a real potential for performance differences under certain scenarios: https://esdiscuss.org/topic/performance-concern-with-let-const

推荐答案

TL;DR

理论上,此循环的未优化版本:

TL;DR

In theory, an unoptimized version of this loop:

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

可能比使用 var 的同一循环的未优化版本慢:

might be slower than an unoptimized version of the same loop with var:

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

因为使用 let 为每次循环迭代创建了一个不同 i 变量,而只有一个ivar.

because a different i variable is created for each loop iteration with let, whereas there's only one i with var.

反对的事实是 var 被提升,因此它在循环外声明,而 let 仅在循环内声明,这可能会提供优化优势.

Arguing against that is the fact the var is hoisted so it's declared outside the loop whereas the let is only declared within the loop, which may offer an optimization advantage.

在实践中,在 2018 年,现代 JavaScript 引擎对循环进行了足够的自省,以了解何时可以优化这种差异.(即使在此之前,您的循环也很可能已经完成了足够的工作,以至于额外的 let 相关开销无论如何都会被冲掉.但现在您甚至不必担心它.)

In practice, here in 2018, modern JavaScript engines do enough introspection of the loop to know when it can optimize that difference away. (Even before then, odds are your loop was doing enough work that the additional let-related overhead was washed out anyway. But now you don't even have to worry about it.)

当心综合基准测试,因为它们极易出错,并且会以真实代码不会触发的方式(无论好坏)触发 JavaScript 引擎优化器.但是,如果您想要一个综合基准,这里有一个:

Beware synthetic benchmarks as they are extremely easy to get wrong, and trigger JavaScript engine optimizers in ways that real code doesn't (both good and bad ways). However, if you want a synthetic benchmark, here's one:

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 上的综合测试没有显着差异.(两种浏览器中的重复测试有一个获胜,或者另一个获胜,并且在这两种情况下都在误差范围内.)但同样,这是一个综合基准,而不是您的代码.当您的代码出现性能问题时,请担心您的代码的性能.

It says that there's no significant difference in that synthetic test on either V8/Chrome or SpiderMonkey/Firefox. (Repeated tests in both browsers have one winning, or the other winning, and in both cases within a margin of error.) But again, it's a synthetic benchmark, not your code. Worry about the performance of your code when and if your code has a performance problem.

作为一种风格问题,如果我在闭包中使用循环变量,我更喜欢 let 以获得范围优势和闭包循环优势.

As a style matter, I prefer let for the scoping benefit and the closure-in-loops benefit if I use the loop variable in a closure.

for 循环中 varlet 之间的重要区别在于,为每次迭代;它解决了经典的循环中的闭包";问题:

The important difference between var and let in a for loop is that a different i is created for each iteration; it addresses the classic "closures in loop" problem:

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 版本慢.

Creating the new EnvironmentRecord for each loop body (spec link) is work, and work takes time, which is why in theory the let version is slower than the var version.

但只有在使用 i 的循环内创建一个函数(闭包)时,差异才有意义,就像我在上面的可运行片段示例中所做的那样.否则,无法观察到区别,可以优化掉.

But the difference only matters if you create a function (closure) within the loop that uses i, as I did in that runnable snippet example above. Otherwise, the distinction can't be observed and can be optimized away.

在 2018 年,看起来 V8(以及 Firefox 中的 SpiderMonkey)正在进行充分的自省,即在不使用 let 的 variable-per- 的循环中没有性能成本迭代语义.请参阅此测试.

Here in 2018, it looks like V8 (and SpiderMonkey in Firefox) is doing sufficient introspection that there's no performance cost in a loop that doesn't make use of let's variable-per-iteration semantics. See this test.

在某些情况下,const 很可能会提供 var 不会提供的优化机会,尤其是对于全局变量.

In some cases, const may well provide an opportunity for optimization that var wouldn't, especially for global variables.

全局变量的问题在于它是全局的;任何代码任何地方都可以访问它.因此,如果您使用 var 声明一个您从不打算更改的变量(并且从不更改您的代码),引擎不能假设它永远不会因为稍后加载的代码或类似.

The problem with a global variable is that it's, well, global; any code anywhere could access it. So if you declare a variable with var that you never intend to change (and never do change in your code), the engine can't assume it's never going to change as the result of code loaded later or similar.

但是,使用 const,您明确地告诉引擎该值不能更改¹.所以它可以自由地做任何它想要的优化,包括发出一个文字而不是一个变量引用到使用它的代码,知道值不能改变.

With const, though, you're explicitly telling the engine that the value cannot change¹. So it's free to do any optimization it wants, including emitting a literal instead of a variable reference to code using it, knowing that the values cannot be changed.

¹ 请记住,对于对象,值是一个对对象的引用,而不是对象本身.因此,使用 const o = {},您可以更改对象的状态 (o.answer = 42),但不能使 o 指向一个新对象(因为这需要更改它包含的对象引用).​​

¹ Remember that with objects, the value is a reference to the object, not the object itself. So with const o = {}, you could change the state of the object (o.answer = 42), but you can't make o point to a new object (because that would require changing the object reference it contains).

当在其他类似 var 的情况下使用 letconst 时,它们不太可能有不同的性能.无论您使用 var 还是 let,此函数都应该具有完全相同的性能,例如:

When using let or const in other var-like situations, they're not likely to have different performance. This function should have exactly the same performance whether you use var or let, for instance:

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


当然,这一切都不太重要,只有当有真正的问题需要解决时才需要担心.


It's all, of course, unlikely to matter and something to worry about only if and when there's a real problem to solve.

相关文章