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

时间:2011-09-04 11:50:13

标签: javascript performance string-concatenation

今天,我阅读this thread关于字符串连接的速度。

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

  

http://jsben.ch/#/OJ3vo

结果与我的想法相反。此外,有很多关于此的文章与this相反地解释。

我可以猜测浏览器在最新版本上针对字符串concat进行了优化,但他们是如何做到的?我们可以说在连接字符串时使用+会更好吗?

更新

因此,在现代浏览器中,字符串连接已经过优化,因此当您想连接字符串时,使用+符号比使用join更快。

但是@Arthur pointed out join如果你真的想用加入字符串并使用分隔符,则会更快。

10 个答案:

答案 0 :(得分:139)

  

浏览器字符串优化已更改字符串连接图片。

     

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

     

- Writing Efficient JavaScript: Chapter 7 – Even Faster Websites

V8 javascript引擎(在Google Chrome中使用)使用this code进行字符串连接:

// 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_FUNCTION(MaybeObject*, Runtime_StringBuilderConcat)文件中搜索{{1}}以查看代码。

答案 1 :(得分:22)

Firefox很快,因为它使用了一种名为Ropes(Ropes: an Alternative to Strings)的东西。绳索基本上只是一个DAG,其中每个Node都是一个字符串。

例如,如果您执行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('')通常只会分配内存以在内存中布置新的字符串。 (也许这应该优化)

答案 2 :(得分:4)

我知道这是一个旧线程,但你的测试不正确。你正在做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()更快。

答案 3 :(得分:3)

这些基准是微不足道的。重复连接相同的三个项目将被内联,结果将被证明是确定性的和memoized,垃圾处理程序将丢弃数组对象(其大小几乎没有任何东西),并且可能只是因为没有推出和弹出堆栈外部引用,因为字符串永远不会改变。如果测试是大量随机生成的字符串,我会更感动。 就像在演出或两个字符串中一样。

Array.join FTW!

答案 4 :(得分:2)

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

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

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

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

答案 5 :(得分:1)

This test显示实际使用带有赋值连接的字符串与使用array.join方法制作的字符串的惩罚。虽然Chrome v31的整体分配速度仍然是其两倍,但它不再像没有使用结果字符串那么大。

答案 6 :(得分:0)

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

我会说String.concat在最新版本的V8中表现更好。但对于Firefox和Opera,Array.join是赢家。

答案 7 :(得分:0)

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

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上进行了测试。 请注意,即使包含数组创建,它也更快!

答案 8 :(得分:0)

截至 2021 年,在 Chrome 上,数组 push+join 对于 10^4 或 10^5 字符串大约慢 10 倍,但对于 10^6 字符串仅慢 1.2 倍。

https://jsben.ch/dhIy

上试试

答案 9 :(得分:-1)

我的猜测是,虽然每个版本都要承担许多连接的成本,但连接版本除此之外还在构建阵列。