为什么字符串连接比数组连接快?


114

今天,我阅读了有关线程串联速度的主题

令人惊讶的是,字符串连接是赢家:

http://jsben.ch/#/OJ3vo

结果与我的想法相反。此外,与有关的文章很多,都与此相反。

我可以猜测浏览器已针对concat最新版本进行了优化,但是如何做到这一点呢?我们可以说+串联字符串时最好使用吗?


更新资料

因此,在现代浏览器中,对字符串串联进行了优化,因此使用+符号比join串联字符串时使用符号要快。

但是@Arthur指出join如果您实际上想使用分隔符连接字符串,则速度会更快。


更新-2020
Chrome:数组join几乎2 times faster是String concat + 参见:https : //stackoverflow.com/a/54970240/984471

注意:

  • join如果有的话,数组更好large strings
  • 如果我们需要several small strings在最终输出中生成,最好使用字符串concat +,否则使用Array会在末尾需要几次Array到String的转换,这会导致性能超载。


1
该代码应该产生500 TB的垃圾,但运行时间为200毫秒。我认为他们只是为字符串分配了更多的空间,当您向其中添加短字符串时,它通常会容纳一个额外的空间。
伊万·库基尔

Answers:


149

浏览器字符串优化已更改了字符串串联图片。

Firefox是第一个优化字符串连接的浏览器。从1.0版开始,在所有情况下,数组技术实际上都比使用plus运算符慢。其他浏览器还优化了字符串连接,因此Safari,Opera,Chrome和Internet Explorer 8也使用plus运算符显示了更好的性能。版本8之前的Internet Explorer没有进行这种优化,因此数组技术始终比plus运算符快。

编写高效的JavaScript:第7章–更快的网站

V8 JavaScript引擎(在Google Chrome中使用)使用以下代码进行字符串连接:

// ECMA-262, section 15.5.4.6
function StringConcat() {
  if (IS_NULL_OR_UNDEFINED(this) && !IS_UNDETECTABLE(this)) {
    throw MakeTypeError("called_on_null_or_undefined", ["String.prototype.concat"]);
  }
  var len = %_ArgumentsLength();
  var this_as_string = TO_STRING_INLINE(this);
  if (len === 1) {
    return this_as_string + %_Arguments(0);
  }
  var parts = new InternalArray(len + 1);
  parts[0] = this_as_string;
  for (var i = 0; i < len; i++) {
    var part = %_Arguments(i);
    parts[i + 1] = TO_STRING_INLINE(part);
  }
  return %StringBuilderConcat(parts, len + 1, "");
}

因此,他们在内部通过创建InternalArray(parts变量)对其进行优化,然后将其填充。这些部分将调用StringBuilderConcat函数。之所以快,是因为StringBuilderConcat函数是一些经过高度优化的C ++代码。在这里引用太长了,但是请在runtime.cc文件中搜索RUNTIME_FUNCTION(MaybeObject*, Runtime_StringBuilderConcat)以查看代码。


4
您忽略了真正有趣的事情,该数组仅用于调用具有不同参数计数的Runtime_StringBuilderConcat。但是真正的工作在那里完成。
evilpie 2011年

41
优化101:您的目标应该是最慢!例如,arr.join vs str+在chrome上,您可以获得(每秒的操作数)25k/s vs 52k/s。在firefox上,新的76k/s vs 212k/s。所以str+比较快。但让我们看看其他浏览器。Opera的速度为43k / s和26k / s。IE给1300/s vs 1002/s。走着瞧吧?在只有浏览器需要进行优化将被关闭使用什么是对所有的人,它并不在所有问题慢越好。因此,这些文章都没有任何有关性能的知识。
gcb

45
@gcb,不应仅使用连接速度更快的浏览器。我95%的用户使用FF和Chrome。我将针对95%的用例进行优化。
Paul Draper 2014年

7
@PaulDraper如果90%的用户使用快速浏览器并且您选择的任何一个选项将获得0.001s,但是如果您选择从0.001s中惩罚其他用户,则10%的用户将获得2s ...决定清楚了。如果您看不到它,那么无论您为谁编码,我都会感到抱歉。
gcb

7
较旧的浏览器最终将消失,但是不太可能有人转换所有这些数组联接。最好为将来编写代码,只要这不会给您当前的用户带来很大的麻烦。与旧的浏览器打交道时,比串联性能要担心的事情还多。
Thomas Higginbotham

23

Firefox之所以快速,是因为它使用了一种叫做Ropes的东西(Ropes:字符串的替代品)。绳索基本上只是DAG,其中每个节点都是一个字符串。

因此,例如,如果您愿意a = 'abc'.concat('def'),则新创建的对象将如下所示。当然,这与内存中的外观并不完全一样,因为您仍然需要一个字段来输入字符串类型,长度和其他内容。

a = {
 nodeA: 'abc',
 nodeB: 'def'
}

b = a.concat('123')

b = {
  nodeA: a, /* {
             nodeA: 'abc',
             nodeB: 'def'
          } */
  nodeB: '123'
}           

因此,在最简单的情况下,VM几乎不需要执行任何操作。唯一的问题是,这会稍微减慢对结果字符串的其他操作。当然,这也减少了内存开销。

另一方面['abc', 'def'].join(''),通常只会分配内存以将新的字符串平放在内存中。(也许应该对此进行优化)


6

我知道这是一个旧线程,但是您的测试不正确。您正在做的output += myarray[i];事情应该更像是output += "" + myarray[i];因为您忘记了,必须将物品与物品粘合在一起。concat代码应类似于:

var output = myarray[0];
for (var i = 1, len = myarray.length; i<len; i++){
    output += "" + myarray[i];
}

这样,由于将元素粘合在一起,您正在执行两项操作,而不是一项。

Array.join() 是比较快的。


我没有你的答案。推杆"" +和原始推杆有什么区别?
Sanghyun Lee,

这是两个操作,而不是每次迭代都执行一次,这会花费更多时间。
亚瑟

1
那为什么我们需要这样呢?如果output没有它,我们已经在粘贴物品。
Sanghyun Lee,

因为这是联接的工作方式。例如,您也可以这样做Array.join(","),这对for循环不起作用
Arthur

哦,我明白了。您是否已经测试过看看join()是否更快?
Sanghyun Lee

5

对于大量数据连接,连接速度更快,因此该问题陈述不正确。

let result = "";
let startTime = new Date().getTime();
for (let i = 0; i < 2000000; i++) {
    result += "x";
}
console.log("concatenation time: " + (new Date().getTime() - startTime));

startTime = new Date().getTime();
let array = new Array(2000000);
for (let i = 0; i < 2000000; i++) {
    array[i] = "x";
}
result = array.join("");
console.log("join time: " + (new Date().getTime() - startTime));

在Chrome 72.0.3626.119,Firefox 65.0.1,Edge 42.17134.1.0上进行了测试。请注意,即使包含数组创建,它也更快!


〜2020年8月。是的。在Chrome中:Array Join时间:462。String Concat(+)时间:827。Join快了将近2倍。
Manohar Reddy Poreddy,

3

那里的基准是微不足道的。内联重复将相同的三个项目内联,结果将被证明是确定性的并被记录下来,垃圾处理程序将仅丢弃数组对象(大小将接近零),并且可能由于没有而被推入栈并弹出外部引用,因为字符串永远不会改变。如果测试是大量随机生成的字符串,我会印象深刻。就像一两​​场弦乐一样。

Array.join FTW!


2

我想说的是,使用字符串可以更容易地预分配更大的缓冲区。每个元素只有2个字节(如果是UNICODE),因此即使您比较保守,也可以为字符串预分配一个很大的缓冲区。由于arrays每个元素都是,所以每个元素都更加“复杂”,Object因此保守的实现将为更少的元素预分配空间。

如果您尝试for(j=0;j<1000;j++)在每一个之前添加一个,for则会看到(在chrome之下)速度差异变小。最终,字符串连接仍然是1.5倍,但小于以前的2.6。

而且必须复制元素,因此Unicode字符可能小于对JS对象的引用。

请注意,JS引擎的许多实现都有可能对单类型数组进行了优化,这将使我所写的内容毫无用处:-)


1

此测试显示了实际使用由分配串联创建的字符串与由array.join方法创建的字符串的区别。虽然Chrome v31的总体分配速度仍然是其两倍,但不再像不使用结果字符串时那样大。


0

这显然取决于javascript引擎的实现。即使对于一个引擎的不同版本,您也可以得到明显不同的结果。您应该做自己的基准来验证这一点。

我想说这String.concat在最新版本的V8 中具有更好的性能。但是对于Firefox和Opera来说,Array.join却是赢家。


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.