【问题标题】:Why is string concatenation faster than array join?为什么字符串连接比数组连接快?
【发布时间】:2011-11-10 01:42:39
【问题描述】:

今天看了this thread关于字符串拼接的速度。

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

http://jsben.ch/#/OJ3vo

结果与我想的相反。此外,还有很多关于这个的文章是相反的,比如this

我可以猜到浏览器在最新版本上被优化为字符串concat,但是他们是怎么做到的呢?我们能说在连接字符串时使用+ 更好吗?


更新

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

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


更新 - 2020 年
Chrome:数组join几乎是2 times faster是字符串连接+ 见:https://stackoverflow.com/a/54970240/984471

作为说明:

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

【问题讨论】:

  • This code 应该产生 500 TB 的垃圾,但它在 200 毫秒内运行。我认为他们只是为一个字符串分配了更多的空间,当你向它添加一个短字符串时,它通常适合一个额外的空间。

标签: javascript performance string-concatenation


【解决方案1】:

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

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

——Writing Efficient JavaScript: Chapter 7 – Even Faster Websites

V8 javascript 引擎(用于谷歌浏览器)使用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.cc 文件中搜索RUNTIME_FUNCTION(MaybeObject*, Runtime_StringBuilderConcat) 以查看代码。

【讨论】:

  • 您遗漏了真正有趣的事情,该数组仅用于调用具有不同参数计数的 Runtime_StringBuilderConcat。但真正的工作是在那里完成的。
  • 优化 101:你应该以最慢为目标!例如,arr.join vs str+,在 chrome 上你会得到(每秒操作数)25k/s vs 52k/s。在 firefox new 上你会得到76k/s vs 212k/s。所以str+ 更快。但让我们看看其他浏览器。 Opera 提供 43k/s 和 26k/s。 IE 给出1300/s vs 1002/s。走着瞧吧? 唯一 需要优化的浏览器最好在所有其他浏览器上使用较慢的浏览器,这根本不重要。所以,这些文章都不了解性能。
  • @gcb,不应该使用唯一加入速度更快的浏览器。我 95% 的用户都有 FF 和 Chrome。我将针对 95% 的用例进行优化。
  • @PaulDraper 如果 90% 的用户使用快速浏览器,并且您选择的任一选项将获得 0.001 秒,但如果您选择惩罚其他用户,则 10% 的用户将获得 2 秒那个 0.001s... 决定很明确。如果你看不到它,我很抱歉你为谁编码。
  • 较旧的浏览器最终会消失,但不太可能有人返回转换所有这些数组连接。只要不会给当前用户带来很大的不便,最好为未来编写代码。在处理旧浏览器时,可能有比连接性能更重要的事情需要担心。
【解决方案2】:

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

【讨论】:

    【解决方案3】:

    对于大量的数据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 上测试。 请注意,即使包含数组创建,它也更快!

    【讨论】:

    • ~2020 年 8 月。是的。在 Chrome 中:数组连接时间:462。字符串连接 (+) 时间:827。连接速度几乎快 2 倍。
    • 再按几次“run code sn-p”看看会发生什么。
    【解决方案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() 更快。

    【讨论】:

    • 我没有得到你的答案。放"" +和原版有什么区别?
    • 每次迭代需要两个操作而不是一个,这需要更多时间。
    • 为什么我们需要放那个?没有它,我们已经将项目粘合到 output
    • 因为这就是 join 的工作方式。例如,您也可以执行 Array.join(","),这将不适用于您的 for 循环
    • 哦,我明白了。您是否已经测试过 join() 是否更快?
    【解决方案5】:

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

    Array.join FTW!

    【讨论】:

      【解决方案6】:

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

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

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

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

      【讨论】:

        【解决方案7】:

        This test 显示了实际使用通过赋值连接生成的字符串与使用 array.join 方法生成的惩罚。虽然在 Chrome v31 中总体赋值速度仍然是两倍,但它不再像不使用结果字符串时那么大。

        【讨论】:

          【解决方案8】:

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

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

          【讨论】:

            【解决方案9】:

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

            试试https://jsben.ch/dhIy

            【讨论】:

            • 链接没有测试
            【解决方案10】:

            我的猜测是,虽然每个版本都花费了许多连接的成本,但连接版本除此之外还在构建数组。

            【讨论】:

              猜你喜欢
              • 2020-12-18
              • 2011-01-20
              • 2011-12-26
              • 2014-02-20
              • 2017-06-30
              • 1970-01-01
              • 2011-11-09
              • 2010-09-21
              相关资源
              最近更新 更多