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

IT技术 javascript performance string-concatenation
2021-01-29 05:22:52

今天,我阅读了有关字符串连接速度的主题

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

http://jsben.ch/#/OJ3vo

结果和我想的相反。此外,大约有这么多的文章,其解释相反像这样

我可以猜测浏览器已针对concat最新版本的字符串进行了优化,但它们是如何做到的?能不能说+在连接字符串的时候用比较好


更新

因此,在现代浏览器中,字符串连接得到了优化,因此使用+符号比join在想要连接字符串使用更快

@Arthur 指出join如果您真的想用分隔符连接字符串,速度会更快


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

作为注释:

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

6个回答

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

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

编写高效的 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)以查看代码。

您忽略了真正有趣的事情,该数组仅用于调用具有不同参数计数的 Runtime_StringBuilderConcat。但真正的工作是在那里完成的。
2021-03-14 05:22:52
@gcb,不应该使用唯一的 join 速度更快的浏览器。我 95% 的用户都使用 FF 和 Chrome。我将针对 95% 的用例进行优化。
2021-03-15 05:22:52
优化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. 走着瞧吧?只有浏览器需要进行优化将被关闭使用什么是对所有的人,它并不在所有问题慢越好。因此,这些文章中没有一篇对性能有任何了解。
2021-03-23 05:22:52
@PaulDraper 如果 90% 的用户使用快速浏览器,并且您选择的任一选项都会使他们获得 0.001 秒,但如果您选择惩罚其他用户,则 10% 的用户将获得 2 秒...决定清楚了。如果你看不到它,我为你编码的人感到抱歉。
2021-03-27 05:22:52
较旧的浏览器最终会消失,但不太可能有人回去转换所有这些数组连接。只要不对当前用户造成重大不便,最好为未来编写代码。在处理旧浏览器时,可能有比串联性能更重要的事情需要担心。
2021-04-10 05:22:52

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

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

哦,我明白了。您是否已经测试过 join() 是否更快?
2021-03-13 05:22:52
这是两次操作,而不是每次迭代一次,这需要更多时间。
2021-03-15 05:22:52
我不明白你的答案。"" +和原来的有什么区别
2021-03-18 05:22:52
因为这就是 join 的工作方式。例如,您还可以执行Array.join(",")这不适用于您的for循环
2021-03-21 05:22:52
为什么我们需要这样写?我们已经在output没有它的情况下粘合物品
2021-04-01 05:22:52

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

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 (+) time:827。Join 快了几乎 2 倍。
2021-03-13 05:22:52
再按几次“运行代码片段”,看看会发生什么。
2021-04-03 05:22:52

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

Array.join FTW!