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

2022-08-30 05:03:11

无论功能差异如何,使用新关键字“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


答案 1

TL;DR

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

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

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

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

因为对于每个循环迭代 ,都会创建一个不同的变量,而 只有一个带有 。iletivar

与此相反的是,它被吊装了,所以它在循环之外声明,而只在循环内声明,这可能提供优化优势。varlet

在实践中,在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

循环中的重要区别在于,每次迭代都会创建不同的循环;它解决了经典的“循环中的闭包”问题:varletfori

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);

为每个循环体(规范链接)创建新的环境记录是工作,工作需要时间,这就是为什么理论上版本比版本慢的原因。letvar

但是,只有在循环中创建一个使用 的函数(闭包)时,差异才重要,就像我在上面的可运行代码片段示例中所做的那样。否则,无法观察到这种区别,并且可以对其进行优化。i

在2018年,看起来V8(以及Firefox中的SpiderMonkey)正在做足够的内省,以至于在不使用的每次迭代变量语义的循环中没有性能成本。请参阅此测试let


在某些情况下,可能会提供优化的机会,但不会,特别是对于全局变量。constvar

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

但是,使用 时,您可以显式告诉引擎该值不能更改¹。因此,可以自由地进行任何优化,包括发出文本而不是对使用它的代码的变量引用,因为知道值无法更改。const

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


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

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

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


答案 2

“LET”在循环声明中更好

在导航器中进行简单的测试(5次),如下所示:

// WITH VAR
console.time("var-time")
for(var i = 0; i < 500000; i++){}
console.timeEnd("var-time")

平均执行时间超过2.5ms

// WITH LET
console.time("let-time")
for(let i = 0; i < 500000; i++){}
console.timeEnd("let-time")

平均执行时间超过1.5ms

我发现循环时间与let更好。