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

IT技术 javascript performance constants v8 let
2021-03-05 04:07:40

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

5个回答

TL; 博士

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

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

可能比相同循环的未优化版本慢var

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

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

与此var相反的let是,the被提升,因此它在循环外声明,而 the仅在循环内声明,这可能提供优化优势。

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

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

它说在 V8/Chrome 或 SpiderMonkey/Firefox 上的综合测试没有显着差异。(在两种浏览器中的重复测试都会有一个获胜,或者另一个获胜,并且在这两种情况下都在误差范围内。)但同样,它是一个综合基准,而不是您的代码。当您的代码出现性能问题时,请担心您的代码性能。

作为风格问题,let如果我在闭包中使用循环变量,我更喜欢范围界定的好处和闭包中的好处。

细节

之间的重要差异varlet在一个for循环是一个不同i是针对每次迭代创建的; 它解决了经典的“循环闭包”问题:

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

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

在 2018 年,看起来 V8(和 Firefox 中的 SpiderMonkey)正在做足够的内省,在不使用let's variable-per-iteration 语义的循环中没有性能成本请参阅此测试


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

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

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

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


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

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

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

@DanM。:好消息,优化似乎已经赶上了,至少在 V8 和 SpiderMonkey 中。:-)
2021-04-17 04:07:40
感谢您的回答 - 我同意,所以我自己已经标准化了使用 var 进行循环操作,如您的第一个 for 循环示例中所述,并且让 / const 用于所有其他声明,假设性能差异基本上不存在,因为性能测试看起来暂时表示。也许以后会添加对 const 的优化。也就是说,除非其他人可以通过代码示例显示出明显的差异。
2021-04-25 04:07:40
截至 2018 年年中,带有 let 和 var 的版本在 Chrome 中具有相同的速度,因此现在不再有区别。
2021-04-28 04:07:40
@sean2078:我let也在循环示例中使用在 99.999% 的情况下,性能差异不值得担心。
2021-05-12 04:07:40
谢谢。很公平。
2021-05-15 04:07:40

“让”在循环声明中更好

在导航器中进行简单的测试(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 的循环时间更好。

我刚刚在 Node v12.5 中试用了这个。我发现平均速度是var=2.6mslet=1.0ms所以让 in Node 的速度快两倍多一点。
2021-05-02 04:07:40
只是为了说明在优化器存在的情况下性能测试很难的通常观点:我认为 let 循环正在被完全优化 - let 只存在于块内并且循环没有副作用,并且 V8 足够聪明,知道它可以只需删除块,然后是循环。var 声明被提升,所以它不知道。你的循环我得到 1ms/0.4ms,但是如果我在循环外有一个变量 j(var 或 let)并且它也增加了,那么我得到 1ms/1.5ms。即 var 循环没有变化,让循环现在需要更长的时间。
2021-05-02 04:07:40
在 Firefox 65.0 中运行它,我的平均速度为var=138.8mslet=4ms这不是打字错误,let现在快了 30 倍以上
2021-05-03 04:07:40
@KaneHooper - 如果您在 Firefox 中有五倍的差异,那么它必须是空循环体。真正的循环没有空体。
2021-05-05 04:07:40
当心合成基准测试,尤其是带有空体循环的基准测试如果你真的在循环中做了一些事情,这个综合基准(再次提防!:-))表明没有显着差异。我还在我的答案中添加了一个,所以它是现场的(不像那些不断消失在我身上的 jsPerf 测试。:-))。重复运行显示一个获胜,或另一个获胜。当然没有定论。
2021-05-14 04:07:40

TJ Crowder的回答非常出色。

这里有一个补充:“在将现有的 var 声明编辑为 const 时,我什么时候才能获得最大的收益?”

我发现最大的性能提升与“导出”函数有关。

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

我意识到,如果您深入了解 const、var 和 let 的工作原理,您可能已经得出了上述结论……但万一您“瞥了一眼”:D。

根据我在进行更新时对节点 v8.12.0 的基准测试的记忆,我的应用程序从大约 240MB RAM 的空闲消耗变为了大约 233MB RAM。

TJ Crowder 的回答很好,但是:

  1. 'let' 是为了使代码更具可读性,而不是更强大
  2. 理论上 let 会比 var 慢
  3. 通过实践编译器不能完全解决(静态分析)一个未完成的程序,所以有时它会错过优化
  4. 在任何情况下使用“让”将需要更多的 CPU 进行内省,必须在 google v8 开始解析时启动工作台
  5. 如果自省失败,'let' 将大力推动 V8 垃圾收集器,它将需要更多的迭代来释放/重用。它也会消耗更多的内存。替补席必须考虑到这些要点
  6. Google Closure 将在 var 中转换 let...

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

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

出于兴趣 -什么理论let比 慢var特别是考虑到对上述答案的评论中的共识表明它更快?
2021-05-09 04:07:40

带有“let”的代码将比带有“var”的代码更优化,因为当作用域到期时,用 var 声明的变量不会被清除,但用 let 声明的变量会被清除。所以 var 使用更多的空间,因为它在循环中使用时会产生不同的版本。

我不确定我是否遵循您所说的var 使用更多空间的意思,因为它在循环中使用时会产生不同的版本。你能分享一个例子吗?
2021-04-19 04:07:40