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

v8 JavaScript performance implications of const, let, and var?

无论功能差异如何,使用新关键字 '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的每个循环迭代创建的,而[=16=只有一个i =].

反对 的事实是 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 的作用域优势和闭包循环优势。

详情

varletfor 循环中的重要区别是每次迭代都会创建不同的 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);

为每个循环体 (spec link) 创建新的 EnvironmentRecord 是工作,工作需要时间,这就是理论上 let 版本比 var 版本慢的原因.

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

在 2018 年,V8(和 Firefox 中的 SpiderMonkey)似乎正在做足够的自省,在不使用 let 的每次迭代变量的循环中没有性能成本语义。参见 this test


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

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

但是,对于 const,您是在明确告诉引擎该值不能更改¹。因此它可以自由地进行任何它想要的优化,包括发出文字而不是对使用它的代码的变量引用,知道这些值无法更改。

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


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

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

当然,这一切都无关紧要,只有当有真正的问题需要解决时才需要担心。

T.J。 Crowder的回答太棒了

这里补充:"When would I get the most bang for my buck on editing existing var declarations to const ?"

我发现最大的性能提升与 "exported" 函数有关。

因此,如果文件 A、B、R 和 Z 正在调用文件 U 中的 "utility" 函数,该函数通常通过您的应用程序使用,则将该实用程序函数切换到 "const" 并且对 const 的父文件引用可以提高性能。在我看来,它并没有显着加快,但对于我的整体式 Frankenstein-ed 应用程序,整体内存消耗减少了大约 1-3%。如果您在云或裸机服务器上花费大量现金,这可能是花 30 分钟梳理并将其中一些 var 声明更新为 const 的好理由。

我意识到,如果您了解了 const、var 和 let 是如何在幕后工作的,那么您可能已经得出了上述结论...但万一您 "glanced" 超过了 :D.

据我所知,我在进行更新时对节点 v8.12.0 进行了基准测试,我的应用程序从闲置消耗 ~240MB RAM 到 ~233MB RAM。

"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 的循环时间更好。

T.J。 Crowder的回答很好但是:

  1. 'let' 是为了让代码更易读,而不是更强大
  2. 理论上 let 会比 var 慢
  3. 根据实践,编译器无法完全解决(静态分析)未完成的程序,因此有时它会错过优化
  4. 在 any-case 中使用 'let' 将需要更多 CPU 进行内省,当 google v8 开始解析
  5. 时必须启动 bench
  6. 如果内省失败 'let' 将对 V8 垃圾收集器施加压力,它将需要更多迭代 free/reuse。它还会消耗更多的内存。板凳必须考虑到这几点
  7. Google 闭包会转换 let in var...

var 和 let 之间的性能差距的影响可以在 real-life 完整的程序中看到,而不是在单个基本循环中看到。

无论如何,在不需要的地方使用 let 会降低代码的可读性。

使用 'let' 的代码将比 'var' 更优化,因为使用 var 声明的变量在范围到期时不会被清除,但使用 let 声明的变量会。所以 var 使用更多 space 因为它在循环中使用时会产生不同的版本。

刚刚做了一些测试,最初我的结论是 var 有很大的不同。我的结果最初表明,在 Const / Let / Var 之间,执行时间的比例从 4 / 4 / 1 到 3 / 3 / 1。

在 29/01/2022 编辑后(根据 jmrk 的评论删除 let 和 const 测试中的全局变量)现在结果看起来与 1 / 1 / 1 相似。 我给出了下面使用的代码。只是让我提一下,我从 的代码开始,做了很多调整和编辑。

我在 w3schools_tryit 编辑器和 Google_scripts

中都做了测试

我的笔记:

  • 在 GoogleScripts 中,第一个测试似乎总是需要更长的时间,no-matter 哪个测试,尤其是对于 <5.000.000 的代表,并且在将它们分成各个功能之前
  • 对于 Reps < 5.000.000 JS 引擎优化是最重要的,结果上下波动,没有安全的结论
  • GoogleScripts 不断地做 ~1.5 倍的时间,我认为这是预期的
  • 当所有测试在单独的函数中分开时,执行速度 at-least 翻了一番,第一次测试的延迟几乎消失了!

请不要评判代码,我确实尝试过但不要假装是 JS 专家。 我很高兴看到您的测试和意见。

function mytests(){
var start = 0;
var tm1=" Const: ", tm2=" Let: ", tm3=" Var: ";
    
start = Date.now();
tstLet();
tm2 += Date.now() - start;

start = Date.now();
tstVar();
tm3 += Date.now() - start;

start = Date.now();
tstConst();
tm1 += (Date.now() - start);

var result = "TIMERS:" + tm1 + tm2 + tm3;
console.log(result);
return result;
}

// with VAR
function tstVar(){
var lmtUp = 50000000;
var i=0;
var item = 2;
var sum = 0;

for(i = 0; i < lmtUp; i++){sum += item;}
item = sum / 1000;
}

// with LET
function tstLet(){
let lmtUp = 50000000;
let j=0;
let item = 2;
let sum=0;

for( j = 0; j < lmtUp; j++){sum += item;}
item = sum/1000;
}

// with CONST
function tstConst(){
const lmtUp = 50000000;
var k=0;
const item = 2;
var sum=0;

for( k = 0; k < lmtUp; k++){sum += item;}
k = sum / 1000;
}