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
的作用域优势和闭包循环优势。
详情
var
和 let
在 for
循环中的重要区别是每次迭代都会创建不同的 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
的情况下使用 let
或 const
时,它们不太可能有不同的性能。无论使用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的回答很好但是:
- 'let' 是为了让代码更易读,而不是更强大
- 理论上 let 会比 var 慢
- 根据实践,编译器无法完全解决(静态分析)未完成的程序,因此有时它会错过优化
- 在 any-case 中使用 'let' 将需要更多 CPU 进行内省,当 google v8 开始解析
时必须启动 bench
- 如果内省失败 'let' 将对 V8 垃圾收集器施加压力,它将需要更多迭代 free/reuse。它还会消耗更多的内存。板凳必须考虑到这几点
- 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;
}
无论功能差异如何,使用新关键字 '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
的作用域优势和闭包循环优势。
详情
var
和 let
在 for
循环中的重要区别是每次迭代都会创建不同的 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
的情况下使用 let
或 const
时,它们不太可能有不同的性能。无论使用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的回答很好但是:
- 'let' 是为了让代码更易读,而不是更强大
- 理论上 let 会比 var 慢
- 根据实践,编译器无法完全解决(静态分析)未完成的程序,因此有时它会错过优化
- 在 any-case 中使用 'let' 将需要更多 CPU 进行内省,当 google v8 开始解析 时必须启动 bench
- 如果内省失败 'let' 将对 V8 垃圾收集器施加压力,它将需要更多迭代 free/reuse。它还会消耗更多的内存。板凳必须考虑到这几点
- 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;
}