ES6 模板文字是否比字符串连接更快?
Are ES6 template literals faster than string concatenation?
在 ES6 中使用字符串连接或模板文字时,HTML 代码生成 运行 在现代浏览器中是否明显更快?
例如:
字符串连接
"<body>"+
"<article>"+
"<time datetime='" + date.toISOString() +"'>"+ date +"</time>"+
"</article>"+
"</body>"
模板文字
`<body>
<article>
<time datetime='${ date.toISOString() }'>${ date }</time>
</article>
</body>`
目前看来字符串连接速度更快:http://jsperf.com/es6-string-literals-vs-string-concatenation
ES6 with variable 19,992,512 ±5.21% 78% slower
String concatenation with variable 89,791,408 ±2.15% fastest
ES6 with function 461,358 ±3.12% 99% slower
String concatenation with function 503,255 ±1.77% 99% slower
我在 Chrome 43.0.2334.0 canary(64 位)上测试了 运行,它使用 V8 4.3.31,启用了 #enable-javascript-harmony
标志。
作为参考,Node.js 上的最新版本(撰写本文时为 0.12.0)使用的是 V8 3.28.73:https://raw.githubusercontent.com/joyent/node/master/ChangeLog
我确信所有可能应用的性能优化还没有应用,所以随着 ES6 接近完成并且这些功能被迁移到稳定分支,期望性能变得更好是合理的.
编辑:感谢 @user1329482、@icl7126、Nicolai Borisik 和 FesterCluck 的评论。自问这个问题以来已经过去了大约 2 年,ES6 浏览器支持大大增加,并且进行了大量的性能优化。这里有一些更新.
编辑:(2020 年 2 月)根据@JorgeFuentesGonzález 的评论和后续确认更新了 Chrome 结果。
在 Chrome(自 59.0.3035 起)中,ES6 字符串文字更快:
ES6 with variable 48,161,401 ±1.07% fastest
String concatenation with variable 27,046,298 ±0.48% 44% slower
ES6 with function 820,441 ±1.10% 98% slower
String concatenation with function 807,088 ±1.08% 98% slower
更新:在 Chrome(自 79.0.3945 起)中,字符串连接速度更快...请参阅评论。
在 Firefox(截至 57.0.0)中,ES6 字符串文字更快:
ES6 with variable 1,924,610,984 ±0.50% fastest
String concatenation with variable 1,876,993,458 ±0.79% 3% slower
ES6 with function 539,762 ±5.04% 100% slower
String concatenation with function 546,030 ±5.88% 100% slower
在 Safari 中(从 11.0.2 开始),这取决于:
ES6 with variable 1,382,752,744 ±0.71% fastest
String concatenation with variable 1,355,512,037 ±0.70% 2% slower
ES6 with function 876,516 ±1.01% 100% slower
String concatenation with function 883,370 ±0.79% 100% slower
使用类型转换字符串时,ES6 字符串字面量更快。但是,当从文字调用函数时,字符串连接速度更快 在此示例中。
如果您真的想深入了解并需要榨取 Safari 的每一滴性能,我建议设置测试以查看if/how 字面效果性能中错误键入的变量和多个引用。
我在 node.js v6.0.0 上进行了简单测试,得到了 几乎相同的性能 。由于测试非常幼稚,所以不要过于相信这些数字。但现在 JIT 编译器似乎生成了非常优化的代码。这让我决定在我的节点应用程序中更喜欢模板而不是串联。
作为参考,这是我使用的代码:
'use strict'
function strConcat(i) {
return 'abc' + i + 'def'
}
function strTemplate(i) {
return `abc${i}def`
}
function run(strategy) {
let before = new Date().getTime()
let len = 0
for ( let i = 0; i < 10000000; i+=1 ) {
len += strategy(i).length
}
console.log(len + ' - ' + ((new Date().getTime()) - before) + 'ms')
}
console.log('strConcat')
run(strConcat)
console.log('strTemplate')
run(strTemplate)
输出为:
strConcat
128888890 - 1904ms
strTemplate
128888890 - 1979ms
我使用 len
来绝对确保优化器不会优化整个循环。不管怎样,这仍然是一个非常简单的测试。也许有人可以做出更复杂的。
对于使用随机数作为字符串的简单测试,两者在 Chrome 和 FF
中非常接近
测试 Chrome 58.0.3029 / Windows 10
String literals 2,996,883 ±2.36% fastest
Operator (+) 3,054,078 ±2.01% fastest
Concat function 2,659,391 ±2.35% 13% slower
在 Firefox 53.0.2 中测试 / Windows 10
String literals 1,923,835 ±1.52% fastest
Operator (+) 1,948,503 ±1.13% fastest
Concat function 1,810,857 ±1.81% 8% slower
TL;DR
就其速度而言,串联更快且更一致。
但是对于 1 或 2 个变量,差异很小(1 亿次调用低于 .3 秒)。
编辑
在第二个 运行 之后,串联似乎是两者中更快的一个。
因此,我想通过提供更广泛的测试来扩展 ,并且还(稍微)研究了这两个函数的可扩展性。
我决定每个函数用四个测试用例,前面一个变量,最后一个,中间一个,中间两个变量。基本设置是相同的。我只是使用函数的 100,000,000 次迭代,这些迭代是 运行 100 次。
我使用相同的机制来防止优化,即获取结果字符串的长度总和并将其记录下来。我还记录了所需的时间(让我猜测需要多长时间),但也将其保存到一个数组中。
之后,我计算了每种方法的平均值、最小值、最大值和标准差。
结果如下:
{
sum: {
t: {
start: 2072751,
mid: 2338476,
end: 2083695,
double: 2950287
},
c: {
start: 2086059,
mid: 2345551,
end: 2074732,
double: 2922929
}
},
avg: {
t: {
start: 20727.51,
mid: 23384.76,
end: 20836.95,
double: 29502.87
},
c: {
start: 20860.59,
mid: 23455.51,
end: 20747.32,
double: 29229.29
}
},
sd: {
t: {
start: 335.6251329981114,
mid: 282.9490809315344,
end: 286.2220947096852,
double: 216.40844045461824
},
c: {
start: 255.4803356424913,
mid: 221.48744862858484,
end: 238.98242111084238,
double: 209.9309074433776
}
},
min: {
t: {
start: 20490,
mid: 23216,
end: 20588,
double: 29271
},
c: {
start: 20660,
mid: 23258,
end: 20534,
double: 28985
}
},
max: {
t: {
start: 23279,
mid: 25616,
end: 22887,
double: 30843
},
c: {
start: 22603,
mid: 25062,
end: 22403,
double: 30536
}
}
}
t
-objects 中的值用于模板,c
-objects 中的值用于连接。 start
表示变量在开头,mid表示在中间,end表示在末尾,double表示有两个变量。 sum
是所有 100 运行 的总和。 avg
是平均值 运行,意思是 sum / 100
。 sd
Here is the easy way out, wikipedia (simple english)。 min
和max
分别是一个运行的最小值和最大值。
结果
对于不位于字符串末尾的单个变量,模板似乎更快,考虑到平均值较低且最小值较低。如果将变量放在字符串的末尾或字符串中有多个变量,连接速度会更快。
尽管在前两个条件下,模板的最小值和平均值都优于其串联模板,但标准差始终较差。差异似乎随着变量的增加而缩小(需要更多的测试)。
由于大多数模板可能不会仅用于字符串中的一个变量,因此可以肯定地说,坚持连接会产生更好的性能。
但差异(至少目前)非常微小。在使用两个变量进行 100,000,000(一亿)次评估时,差异仅为 273.58 毫秒,大约四分之一秒...
第二个运行
第二个 运行 看起来有些不同。除了最大值、平均绝对偏差和标准偏差外,每个测量都证明级联比模板更快。
当变量位于字符串末尾或字符串中有两个变量时,上述三个测量值的模板值较低(因此更好)。
结果如下:
{
"sum": {
"t": {
"start": 1785103,
"mid": 1826679,
"end": 1719594,
"double": 2110823,
"many": 4153368
},
"c": {
"start": 1720260,
"mid": 1799579,
"end": 1716883,
"double": 2097473,
"many": 3836265
}
},
"avg": {
"t": {
"start": 17851.03,
"mid": 18266.79,
"end": 17195.94,
"double": 21108.23,
"many": 41533.68
},
"c": {
"start": 17202.6,
"mid": 17995.79,
"end": 17168.83,
"double": 20974.73,
"many": 38362.65
}
},
"sd": {
"t": {
"start": 858.7857061572462,
"mid": 886.0941856823124,
"end": 786.5366719994689,
"double": 905.5376950188214,
"many": 1744.9005638144542
},
"c": {
"start": 599.0468429096342,
"mid": 719.1084521127534,
"end": 935.9367719563112,
"double": 991.5642274204934,
"many": 1465.1116774840066
}
},
"aad": {
"t": {
"start": 579.1207999999996,
"mid": 576.5628000000003,
"end": 526.8268,
"double": 586.9651999999998,
"many": 1135.9432000000002
},
"c": {
"start": 467.96399999999966,
"mid": 443.09220000000016,
"end": 551.1318000000008,
"double": 610.2321999999999,
"many": 1020.1310000000003
}
},
"min": {
"t": {
"start": 16932,
"mid": 17238,
"end": 16387,
"double": 20016,
"many": 39327
},
"c": {
"start": 16477,
"mid": 17137,
"end": 16226,
"double": 19863,
"many": 36424
}
},
"max": {
"t": {
"start": 23310,
"mid": 24102,
"end": 21258,
"double": 26883,
"many": 49103
},
"c": {
"start": 19328,
"mid": 23203,
"end": 22859,
"double": 26875,
"many": 44352
}
},
"median": {
"t": {
"start": 17571,
"mid": 18062,
"end": 16974,
"double": 20874,
"many": 41171.5
},
"c": {
"start": 16893.5,
"mid": 18213,
"end": 17016.5,
"double": 20771,
"many": 38849
}
}
}
我认为上面的基准没有用。插值或连接的结果尚未使用。所以是的,连接非常快,因为那里没有字符串应对,结果字符串只链接到父字符串。但是,如果您要尝试结果字符串或与另一个字符串进行比较,该字符串将被序列化为平面字符串,是的,这将需要一些时间。因此,在实际情况下,插值对于 CPU 和内存使用比串联更有效。
在 ES6 中使用字符串连接或模板文字时,HTML 代码生成 运行 在现代浏览器中是否明显更快?
例如:
字符串连接
"<body>"+
"<article>"+
"<time datetime='" + date.toISOString() +"'>"+ date +"</time>"+
"</article>"+
"</body>"
模板文字
`<body>
<article>
<time datetime='${ date.toISOString() }'>${ date }</time>
</article>
</body>`
目前看来字符串连接速度更快:http://jsperf.com/es6-string-literals-vs-string-concatenation
ES6 with variable 19,992,512 ±5.21% 78% slower
String concatenation with variable 89,791,408 ±2.15% fastest
ES6 with function 461,358 ±3.12% 99% slower
String concatenation with function 503,255 ±1.77% 99% slower
我在 Chrome 43.0.2334.0 canary(64 位)上测试了 运行,它使用 V8 4.3.31,启用了 #enable-javascript-harmony
标志。
作为参考,Node.js 上的最新版本(撰写本文时为 0.12.0)使用的是 V8 3.28.73:https://raw.githubusercontent.com/joyent/node/master/ChangeLog
我确信所有可能应用的性能优化还没有应用,所以随着 ES6 接近完成并且这些功能被迁移到稳定分支,期望性能变得更好是合理的.
编辑:感谢 @user1329482、@icl7126、Nicolai Borisik 和 FesterCluck 的评论。自问这个问题以来已经过去了大约 2 年,ES6 浏览器支持大大增加,并且进行了大量的性能优化。这里有一些更新.
编辑:(2020 年 2 月)根据@JorgeFuentesGonzález 的评论和后续确认更新了 Chrome 结果。
在 Chrome(自 59.0.3035 起)中,ES6 字符串文字更快:
ES6 with variable 48,161,401 ±1.07% fastest
String concatenation with variable 27,046,298 ±0.48% 44% slower
ES6 with function 820,441 ±1.10% 98% slower
String concatenation with function 807,088 ±1.08% 98% slower
更新:在 Chrome(自 79.0.3945 起)中,字符串连接速度更快...请参阅评论。
在 Firefox(截至 57.0.0)中,ES6 字符串文字更快:
ES6 with variable 1,924,610,984 ±0.50% fastest
String concatenation with variable 1,876,993,458 ±0.79% 3% slower
ES6 with function 539,762 ±5.04% 100% slower
String concatenation with function 546,030 ±5.88% 100% slower
在 Safari 中(从 11.0.2 开始),这取决于:
ES6 with variable 1,382,752,744 ±0.71% fastest
String concatenation with variable 1,355,512,037 ±0.70% 2% slower
ES6 with function 876,516 ±1.01% 100% slower
String concatenation with function 883,370 ±0.79% 100% slower
使用类型转换字符串时,ES6 字符串字面量更快。但是,当从文字调用函数时,字符串连接速度更快 在此示例中。
如果您真的想深入了解并需要榨取 Safari 的每一滴性能,我建议设置测试以查看if/how 字面效果性能中错误键入的变量和多个引用。
我在 node.js v6.0.0 上进行了简单测试,得到了 几乎相同的性能 。由于测试非常幼稚,所以不要过于相信这些数字。但现在 JIT 编译器似乎生成了非常优化的代码。这让我决定在我的节点应用程序中更喜欢模板而不是串联。
作为参考,这是我使用的代码:
'use strict'
function strConcat(i) {
return 'abc' + i + 'def'
}
function strTemplate(i) {
return `abc${i}def`
}
function run(strategy) {
let before = new Date().getTime()
let len = 0
for ( let i = 0; i < 10000000; i+=1 ) {
len += strategy(i).length
}
console.log(len + ' - ' + ((new Date().getTime()) - before) + 'ms')
}
console.log('strConcat')
run(strConcat)
console.log('strTemplate')
run(strTemplate)
输出为:
strConcat
128888890 - 1904ms
strTemplate
128888890 - 1979ms
我使用 len
来绝对确保优化器不会优化整个循环。不管怎样,这仍然是一个非常简单的测试。也许有人可以做出更复杂的。
对于使用随机数作为字符串的简单测试,两者在 Chrome 和 FF
中非常接近测试 Chrome 58.0.3029 / Windows 10
String literals 2,996,883 ±2.36% fastest
Operator (+) 3,054,078 ±2.01% fastest
Concat function 2,659,391 ±2.35% 13% slower
在 Firefox 53.0.2 中测试 / Windows 10
String literals 1,923,835 ±1.52% fastest
Operator (+) 1,948,503 ±1.13% fastest
Concat function 1,810,857 ±1.81% 8% slower
TL;DR
就其速度而言,串联更快且更一致。 但是对于 1 或 2 个变量,差异很小(1 亿次调用低于 .3 秒)。
编辑
在第二个 运行 之后,串联似乎是两者中更快的一个。
因此,我想通过提供更广泛的测试来扩展
我决定每个函数用四个测试用例,前面一个变量,最后一个,中间一个,中间两个变量。基本设置是相同的。我只是使用函数的 100,000,000 次迭代,这些迭代是 运行 100 次。 我使用相同的机制来防止优化,即获取结果字符串的长度总和并将其记录下来。我还记录了所需的时间(让我猜测需要多长时间),但也将其保存到一个数组中。
之后,我计算了每种方法的平均值、最小值、最大值和标准差。
结果如下:
{
sum: {
t: {
start: 2072751,
mid: 2338476,
end: 2083695,
double: 2950287
},
c: {
start: 2086059,
mid: 2345551,
end: 2074732,
double: 2922929
}
},
avg: {
t: {
start: 20727.51,
mid: 23384.76,
end: 20836.95,
double: 29502.87
},
c: {
start: 20860.59,
mid: 23455.51,
end: 20747.32,
double: 29229.29
}
},
sd: {
t: {
start: 335.6251329981114,
mid: 282.9490809315344,
end: 286.2220947096852,
double: 216.40844045461824
},
c: {
start: 255.4803356424913,
mid: 221.48744862858484,
end: 238.98242111084238,
double: 209.9309074433776
}
},
min: {
t: {
start: 20490,
mid: 23216,
end: 20588,
double: 29271
},
c: {
start: 20660,
mid: 23258,
end: 20534,
double: 28985
}
},
max: {
t: {
start: 23279,
mid: 25616,
end: 22887,
double: 30843
},
c: {
start: 22603,
mid: 25062,
end: 22403,
double: 30536
}
}
}
t
-objects 中的值用于模板,c
-objects 中的值用于连接。 start
表示变量在开头,mid表示在中间,end表示在末尾,double表示有两个变量。 sum
是所有 100 运行 的总和。 avg
是平均值 运行,意思是 sum / 100
。 sd
Here is the easy way out, wikipedia (simple english)。 min
和max
分别是一个运行的最小值和最大值。
结果
对于不位于字符串末尾的单个变量,模板似乎更快,考虑到平均值较低且最小值较低。如果将变量放在字符串的末尾或字符串中有多个变量,连接速度会更快。
尽管在前两个条件下,模板的最小值和平均值都优于其串联模板,但标准差始终较差。差异似乎随着变量的增加而缩小(需要更多的测试)。
由于大多数模板可能不会仅用于字符串中的一个变量,因此可以肯定地说,坚持连接会产生更好的性能。 但差异(至少目前)非常微小。在使用两个变量进行 100,000,000(一亿)次评估时,差异仅为 273.58 毫秒,大约四分之一秒...
第二个运行
第二个 运行 看起来有些不同。除了最大值、平均绝对偏差和标准偏差外,每个测量都证明级联比模板更快。
当变量位于字符串末尾或字符串中有两个变量时,上述三个测量值的模板值较低(因此更好)。
结果如下:
{
"sum": {
"t": {
"start": 1785103,
"mid": 1826679,
"end": 1719594,
"double": 2110823,
"many": 4153368
},
"c": {
"start": 1720260,
"mid": 1799579,
"end": 1716883,
"double": 2097473,
"many": 3836265
}
},
"avg": {
"t": {
"start": 17851.03,
"mid": 18266.79,
"end": 17195.94,
"double": 21108.23,
"many": 41533.68
},
"c": {
"start": 17202.6,
"mid": 17995.79,
"end": 17168.83,
"double": 20974.73,
"many": 38362.65
}
},
"sd": {
"t": {
"start": 858.7857061572462,
"mid": 886.0941856823124,
"end": 786.5366719994689,
"double": 905.5376950188214,
"many": 1744.9005638144542
},
"c": {
"start": 599.0468429096342,
"mid": 719.1084521127534,
"end": 935.9367719563112,
"double": 991.5642274204934,
"many": 1465.1116774840066
}
},
"aad": {
"t": {
"start": 579.1207999999996,
"mid": 576.5628000000003,
"end": 526.8268,
"double": 586.9651999999998,
"many": 1135.9432000000002
},
"c": {
"start": 467.96399999999966,
"mid": 443.09220000000016,
"end": 551.1318000000008,
"double": 610.2321999999999,
"many": 1020.1310000000003
}
},
"min": {
"t": {
"start": 16932,
"mid": 17238,
"end": 16387,
"double": 20016,
"many": 39327
},
"c": {
"start": 16477,
"mid": 17137,
"end": 16226,
"double": 19863,
"many": 36424
}
},
"max": {
"t": {
"start": 23310,
"mid": 24102,
"end": 21258,
"double": 26883,
"many": 49103
},
"c": {
"start": 19328,
"mid": 23203,
"end": 22859,
"double": 26875,
"many": 44352
}
},
"median": {
"t": {
"start": 17571,
"mid": 18062,
"end": 16974,
"double": 20874,
"many": 41171.5
},
"c": {
"start": 16893.5,
"mid": 18213,
"end": 17016.5,
"double": 20771,
"many": 38849
}
}
}
我认为上面的基准没有用。插值或连接的结果尚未使用。所以是的,连接非常快,因为那里没有字符串应对,结果字符串只链接到父字符串。但是,如果您要尝试结果字符串或与另一个字符串进行比较,该字符串将被序列化为平面字符串,是的,这将需要一些时间。因此,在实际情况下,插值对于 CPU 和内存使用比串联更有效。