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


88

无论功能上的差异如何,相对于“ var”而言,使用新关键字“ let”和“ const”是否会对性能产生任何普遍或特定的影响?

运行程序后:

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


我认为这取决于用法,例如,let在块作用域中使用的性能应该比var,后者没有块作用域,而只有函数作用域的性能更好。
adeneo '16

如果我可以问,那为什么@adeneo?
sean2078 '16

1
@ sean2078-如果您需要声明一个仅位于块范围内的变量,let则可以这样做,然后进行垃圾回收,而var函数范围内的则不必以相同的方式工作。我再想想,它是如此具体的使用情况,这两个letconst 可以是更好的性能,但不会永远。
adeneo '16

1
我对所引用的代码如何显示var和之间的区别感到困惑let:它根本没有使用let
TJ Crowder

1
当前不是-只是const vs. var ..最初源自gist.github.com/srikumarks/1431640(归功于srikumarks),但是有人提出了将代码置入疑问的请求
sean2078 16/10/16

Answers:


116

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引擎优化器。但是,如果您要使用综合基准,请使用以下基准:

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

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

细节

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

为每个循环主体(spec链接)创建新的EnvironmentRecord是可行的,并且需要花费时间,因此从理论上讲,该let版本比该var版本要慢。

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

在2018年,V8(以及Firefox中的SpiderMonkey)似乎进行了充分的自省,发现在不使用let``每次迭代变量''语义的循环中没有性能成本。看到这个测试


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

全局变量的问题在于它是全局的。任何地方的任何代码可以访问它。因此,如果您声明一个var永不打算更改的变量(也决不要更改代码),那么引擎就无法假设它永远不会更改,因为以后加载的代码或类似的代码都不会改变。

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

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


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

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

当然,所有这些都不大可能重要,只有在真正需要解决的问题时,才需要担心。


感谢您的回答-我同意,因此对我自己来说,已经标准化了使用var进行循环操作,如您在1st for loop示例中所述,对于所有其他声明,让/ const假定性能差异似乎不存在,因为性能测试似乎就不存在了。现在指示。也许以后,将在const上添加优化。也就是说,除非其他人可以通过代码示例显示出明显的区别。
sean2078 '16

@ sean2078:我let也在循环示例中使用。在99.999%的情况下,性能差异不值得担心。
TJ Crowder

2
截至2018年中,具有let和var的版本在Chrome中具有相同的速度,因此现在不再存在差异。
最大

1
@DanM .:好消息,优化似乎已经赶上了,至少在V8和SpiderMonkey中。:-)
TJ Crowder

1
谢谢。很公平。
hypers

18

“ LET”更好地体现在循环声明中

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

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

平均执行时间超过2.5毫秒

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

平均执行时间超过1.5毫秒

我发现让let的循环时间更好。


6
在Firefox 65.0中运行它,我的平均速度为var=138.8mslet=4ms。那不是打字错误,let现在快30倍以上
Katamari

6
我刚刚在Node v12.5中进行了试用。我发现平均速度是var=2.6mslet=1.0ms。所以让Node快一倍以上。
凯恩·胡珀

2
通常,在存在优化器的情况下很难进行性能测试:我认为let循环已被完全优化-let仅存在于块内,并且循环没有副作用,V8足够聪明,可以知道只需删除块,然后循环。var声明已吊起,因此无法知道。您的循环原样为1ms / 0.4ms,但是,如果在循环外部我都拥有一个变量j(var或let),并且该变量也递增,那么我的循环数为1ms / 1.5ms。即var循环没有变化,现在让循环花费更长的时间。
阮史密斯

@KaneHooper-如果您在Firefox中有五倍的差异,那一定是做它的空循环主体。实循环没有空的主体。
TJ Crowder

注意合成基准,尤其是带有空主体的循环基准。如果您实际上在循环中执行某项操作,则此综合基准测试(再次提防!:-))表明没有显着差异。我还在答案中添加了一个,所以它是在现场的(不像那些不断消失在我身上的jsPerf测试::-))。重复运行显示一个获胜,或另一个获胜。当然没有定论。
TJ Crowder

8

TJ Crowder的答案非常好。

这是以下内容的补充:“何时将现有的var声明编辑为const,我可以获得最大的收益?”

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

因此,如果文件A,B,R和Z正在调用文件U(通常通过您的应用程序使用)中的“实用程序”功能,则将该实用程序功能切换到“ const”,并且父文件对const的引用可能会失败改善性能。在我看来,它的速度并没有明显提高,但是对于我的单片科学怪人版应用而言,整体内存消耗却减少了1-3%。如果您要在云或裸机服务器上花费大量现金,那么花30分钟进行梳理并将其中一些var声明更新为const可能是一个很好的理由。

我意识到,如果您读懂const,var的方法,然后在幕后工作的话,您可能已经得出了上述结论……但是如果您“瞥了一眼”它,:D。

从我记得在进行更新时在节点v8.12.0上进行基准测试的情况来看,我的应用程序从约240MB RAM的闲置消耗变为了约233MB RAM。


2

TJ Crowder的回答很好,但:

  1. 使“ let”使代码更具可读性,而不是更强大
  2. 从理论上讲,我们会比var慢
  3. 通过实践,编译器无法完全解决(未完成的静态分析)未完成的程序,因此有时它将错过优化
  4. 在任何情况下,使用“ let”进行内省都会需要更多的CPU,因此必须在Google v8开始解析时启动基准
  5. 如果自省失败,则“ let”将推动V8垃圾收集器的运行,它将需要更多的迭代来释放/重用。它也会消耗更多的RAM。替补席必须考虑到这些要点
  6. Google Closure将转换let in var ...

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

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

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.