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

Why is string concatenation faster than array join?

本文关键字:连接 数组 字符串 为什么      更新时间:2023-09-26

今天,我读了一个关于字符串连接速度的帖子。

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

http://jsben.ch//OJ3vo

结果与我所想的相反。除此之外,有许多文章对此作了相反的解释。

我可以猜测浏览器在最新版本上优化为字符串concat,但他们如何做到这一点?我们能说在连接字符串时使用+更好吗?


所以,在现代浏览器中字符串连接是优化的,所以当你想要连接字符串时,使用+符号比使用join更快。

但是@Arthur指出,如果你真的想用分隔符连接字符串,join会更快。


Update - 2020
Chrome:数组join几乎是2 times faster是字符串concat +参见:https://stackoverflow.com/a/54970240/984471

作为提示:

  • 数组join如果你有large strings
  • 如果我们需要在最终输出中生成several small strings,最好使用字符串concat +,否则使用Array将需要在最后进行多次数组到字符串的转换,这是性能过载。

浏览器字符串优化改变了字符串连接图。

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

-编写高效JavaScript:第七章-更快的网站

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++代码。这里引号太长了,在运行时搜索。cc文件供RUNTIME_FUNCTION(MaybeObject*, Runtime_StringBuilderConcat)查看代码。

Firefox速度很快,因为它使用了一种叫做Ropes (Ropes: a Alternative to Strings)的东西。rope基本上就是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('')通常只是分配内存来将新字符串平铺在内存中。(也许这应该优化)

对于大量的数据连接是更快的,所以问题陈述不正确。

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

我知道这是一个旧的线程,但你的测试是不正确的。你在做output += myarray[i];,而它应该更像output += "" + myarray[i];,因为你忘记了,你必须把项目粘在一起。连接代码应该类似于:

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

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

Array.join()更快。

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

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

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

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

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

数组。加入增值!

这个测试显示了实际使用由赋值连接组成的字符串与由数组组成的字符串的区别。连接方法。虽然分配的总体速度仍然是Chrome v31的两倍,但它不再像不使用结果字符串时那么大。

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

试试https://jsben.ch/dhIy

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

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

我的猜测是,虽然每个版本都在消耗许多连接的成本,但连接版本正在构建数组。