【问题标题】:Are ES6 template literals faster than string concatenation?ES6 模板文字是否比字符串连接更快?
【发布时间】:2015-05-17 07:25:50
【问题描述】:

在 ES6 中使用字符串连接或模板文字时,HTML 代码生成在现代浏览器中的运行速度是否明显加快?

例如:

字符串连接

"<body>"+
  "<article>"+
    "<time datetime='" + date.toISOString() +"'>"+ date +"</time>"+
  "</article>"+
"</body>"

模板文字

`<body>
  <article>
    <time datetime='${ date.toISOString() }'>${ date }</time>
  </article>
</body>`

【问题讨论】:

  • 字符串连接速度不够慢,还有明显的改进空间。像 mustache/underscore/handlebars 这样的流行模板比串联或模板文字慢几十到几百倍。
  • 从理论上讲(除非编译了 JS),模板文字会更慢,因为无论占位符是否存在,都需要解析“字符串”。 JS 引擎将解析模板文字一次,以便后续使用与连接字符串一样快;这意味着唯一的区别是解析模板文字一次所需的时间。
  • 字符串连接当然更快。没有进行解析。它只是增加字符串的长度。我仍然会使用模板文字。
  • 我已投票结束此问题,因为结果尚无定论。 1. 速度取决于引擎——至少 Chrome 和 Firefox 之间可能存在(并且在撰写本文时存在)差异。 2. 速度将取决于如何使用连接/模板以及使用什么数据。 3. 这些可以在同一引擎的不同版本之间更改。 4. 微基准测试会产生误导性结果。 5. 速度上的差异(如果有的话)可能可以忽略不计。在撰写本文时,它

标签: javascript string performance templates ecmascript-6


【解决方案1】:

目前看来字符串连接更快: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 接近完成并且这些特性被迁移到稳定分支,期望性能变得更好是合理的。


编辑:感谢 cmets @user1329482、@icl7126、Nicolai Borisik 和 FesterCluck。现在问这个问题已经过去了大约 2 年,对 ES6 浏览器的支持已经大大增加,并且已经进行了大量的性能优化。以下是一些更新

编辑:(2020 年 2 月)根据 @JorgeFuentesGonzález cmets 和后续确认更新了 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 起),字符串连接速度更快...参见 cmets。

在 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 中榨取每一滴性能,我建议您设置测试,以查看是否/如何错误键入变量以及在文字效果性能中的多个引用。

【讨论】:

  • Firefox 50 64bit - ES6 与串联 1,423,816,207 Ops/s 的速度相同。
  • Safari 9.0 ES6 插值比串联快大约 34% Chrome 55 仍然 ES6 插值慢得多 Firefox 50 相同速度
  • 模板字符串现在比字符串连接快一个数量级。请参阅给定 jsperf 的第 14 版,从技术上讲,它是最准确、最公正的版本,同时保留了功能方面。第 17 版偏差最小,但不切实际。
  • 您传递的 jsperf 链接,在“修订版 1”中,字符串文字仍然比最新 Chrome 下的字符串连接慢:u.teknik.io/nPmY8.png
  • jsperf 结果文件被删除。上传了这个没有过期:u.teknik.io/02OVr.png
【解决方案2】:

我在 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 绝对确保优化器不会优化整个循环。无论如何,这仍然是一个非常简单的测试。也许有人可以做一个更复杂的。

【讨论】:

  • 我运行了类似的基准测试并得到了类似的结果。我的基准包括更多的字符串,其中一些更长。插值的性能略好于串联。
【解决方案3】:

TL;DR

就其速度而言,串联更快且更一致。 但是对于 1 或 2 个变量,差异很小(1 亿次调用不到 0.3 秒)。

编辑

在第二次运行之后,似乎串联是两者中最快的。


所以,我想通过提供一个更广泛的测试来扩展 analog-nico's answer,并且还(有点)研究这两个函数的可扩展性。

Code on pastebin

我决定为每个函数使用四个测试用例,前面有一个变量,最后一个,中间一个,中间两个变量。基本设置是一样的。我只是使用函数的 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 / 100sdHere is the easy way out, wikipedia (simple english)minmax 分别是一次运行的最小值和最大值。

结果

考虑到平均值较低且最小值较低,对于不位于字符串末尾的单个变量,模板似乎更快。如果将变量放在字符串的末尾或字符串中有多个变量,则连接会更快。

尽管就前两个条件而言,模板的最小值和平均值都优于它们的串联对应物,但标准差始终更差。随着更多变量(需要更多测试),差异似乎缩小了。

由于大多数模板可能不会仅用于字符串中的一个变量,因此可以说坚持串联会产生更好的性能。 但差异(至少目前)非常微小。在具有两个变量的 100,000,000(1 亿)次评估中,差异仅为 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
    }
  }
}

The code is here

【讨论】:

  • 我目前正在运行我的测试脚本的新版本。它包括absolute average meanmedian。它还用 10 个要替换的变量对运行时进行基准测试。
【解决方案4】:

对于使用随机数作为字符串的简单测试,两者在 Chrome 和 FF 中都非常接近

在 Chrome 58.0.3029 / Windows 10 中测试

字符串字面量 2,996,883 ±2.36% 最快

算子 (+) 3,054,078 ±2.01% 最快

Concat 函数 2,659,391 ±2.35% 慢 13%

在 Firefox 53.0.2 / Windows 10 中测试

字符串字面量 1,923,835 ±1.52% 最快

算子 (+) 1,948,503 ±1.13% 最快

Concat 函数 1,810,857 ±1.81% 慢 8%

Test here at jsperf

【讨论】:

    【解决方案5】:

    我认为上面的基准没有用。未使用插值或连接的结果。所以是的,连接非常快,因为那里没有字符串处理,结果字符串只有到父字符串的链接。但是,如果您要尝试结果字符串或与另一个字符串进行比较,该字符串将被序列化为平面字符串,是的,这需要一些时间。因此,在实际情况下,插值对于 CPU 和内存的使用可能比串联更有效。

    【讨论】:

      猜你喜欢
      • 2016-09-11
      • 2020-12-18
      • 2015-02-18
      • 2016-09-19
      • 2011-11-27
      • 1970-01-01
      相关资源
      最近更新 更多