文字列の連結が配列結合よりも速いのはなぜですか?


114

今日、私はこのスレッドを読んで、文字列連結の速度について読みました。

驚いたことに、文字列の連結が勝者でした。

http://jsben.ch/#/OJ3vo

結果は私が思っていたのとは正反対でした。また、同様に反対の説明このことについて多くの記事があり、これは

ブラウザはconcat最新バージョンで文字列に最適化されていると思いますが、どうやってそれを行うのですか?+文字列を連結するときに使用する方が良いと言えますか?


更新

使用するのでだから、最近のブラウザで文字列の連結は、最適化されている+兆候が速く使うよりjoinあなたがしたいときCONCATENATE文字列。

しかし、@ Arthurは、文字列をセパレータで結合しjoinたい場合は、それがより速いと指摘しました。


更新-2020
Chrome:配列joinはほぼ2 times faster文字列連結です+ 参照:https : //stackoverflow.com/a/54970240/984471

注として:

  • joinあなたが持っている場合、配列はより良いですlarge strings
  • several small strings最終出力で生成する必要がある場合は、文字列concatを使用することをお勧めします。+そうしないと、配列を使用すると、最後にいくつかの配列から文字列への変換が必要になるため、パフォーマンスが過負荷になります。


1
このコードは500テラバイトのゴミを生成すると想定されていますが、200ミリ秒で実行されます。文字列用に少しだけ多くのスペースを割り当てるだけだと思います。短い文字列を追加すると、通常は余分なスペースに収まります。
Ivan Kuckir 2017

回答:


149

ブラウザの文字列最適化により、文字列連結の図が変更されました。

Firefoxは文字列連結を最適化する最初のブラウザでした。バージョン1.0から、配列手法は実際にはすべての場合にプラス演算子を使用するよりも低速です。他のブラウザも文字列連結を最適化しているため、Safari、Opera、Chrome、およびInternet Explorer 8でも、plus演算子を使用するとパフォーマンスが向上します。バージョン8より前のInternet Explorerにはそのような最適化がなかったため、配列手法は常にplus演算子よりも高速です。

効率的なJavaScriptの記述:第7章–さらに高速なWebサイト

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で(1秒あたりの操作数)25k/s vs 52k/sです。新しいFirefoxで取得し76k/s vs 212k/sます。とてもstr+速いです。しかし、他のブラウザを見てみましょう。Operaは43k / s対26k / sを提供します。IEは与える1300/s vs 1002/s。何が起こるか見ますか?のみ NEEDの最適化は、それがすべてでは問題ではない他のすべて、上の遅いものを使用したほうが良いだろうとブラウザ。したがって、これらの記事はどれもパフォーマンスについて何も理解していません。
gcb 2013

45
@gcb、結合が高速な唯一のブラウザは使用しないでください。ユーザーの95%がFFとChromeを使用しています。95%のユースケースに最適化します。
Paul Draper 14

7
ユーザーの90%が高速のブラウザを使用していて、どちらのオプションを選択しても@PaulDraperは0.001秒を獲得しますが、他のユーザーにその0.001秒からペナルティを課すことを選択すると、ユーザーの10%が2秒を獲得します...決定明確です。あなたがそれを見ることができないなら、あなたがコードを書いた人は誰でもごめんなさい。
gcb 2016

7
古いブラウザは最終的にはなくなるでしょうが、誰かが戻ってこれらの配列結合をすべて変換する可能性は低いです。現在のユーザーにとって大きな不便でない限り、将来のためにコーディングすることをお勧めします。古いブラウザを扱うときは、連結のパフォーマンスよりも、気にする必要のある重要なことがあります。
Thomas Higginbotham

23

Firefoxは、Ropes(Ropes: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('')通常はメモリを割り当てて、新しい文字列をメモリ内でフラットに配置します。(多分これは最適化されるべきです)


6

これは古いスレッドですが、テストが正しくありません。あなたは何かを一緒に接着output += myarray[i];する必要がoutput += "" + myarray[i];あることを忘れているので、もっと似ているはずですが、あなたはやっています。連結コードは次のようになります。

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

このように、要素を接着することにより、1つではなく2つの操作を実行しています。

Array.join() より速いです。


答えがわかりません。パッティング"" +とオリジナルの違いは何ですか?
Sanghyun Lee

より多くの時間がかかる反復ごとに1つではなく2つの操作です。
Arthur

1
そして、なぜそれを置く必要があるのですか?私たちはすでにoutputそれなしでアイテムを接着しています。
Sanghyun Lee

これがjoinの動作方法です。たとえば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の場合:配列結合時間:462。文字列連結(+)時間:827。結合はほぼ2倍高速です。
Manohar Reddy Poreddy

3

そこのベンチマークは取るに足らないものです。同じ3つの項目を繰り返し連結することはインライン化され、結果は確定的であることが証明され、ガベージハンドラーは配列オブジェクト(サイズが何もないもの)を捨てるだけで、おそらくないためにスタックからプッシュおよびポップされます外部参照と文字列が変更されることはありません。テストがランダムに生成された多数の文字列であった場合、私はもっと感銘を受けるでしょう。ギグや2つ分のストリングのように。

Array.join FTW!


2

文字列を使用すると、大きなバッファを事前に割り当てる方が簡単だと思います。各要素は2バイトのみ(UNICODEの場合)なので、保守的であっても、文字列用にかなり大きなバッファを事前に割り当てることができます。arrays各要素、各要素があるため、より「複雑」であるObjectので、保守的な実装は、以下の要素のためのスペースを事前に割り当てるであろう。

for(j=0;j<1000;j++)before each を追加しようとすると、for(クロムの下で)速度の差が小さくなることがわかります。結局、それは文字列連結の場合はまだ1.5倍でしたが、以前の2.6よりは小さかったです。

要素をコピーする必要がある場合、Unicode文字はおそらくJSオブジェクトへの参照よりも小さいです。

JSエンジンの多くの実装が単一タイプの配列を最適化している可能性があることに注意してください。


1

このテストは、代入連結で作成された文字列とarray.joinメソッドで作成された文字列を実際に使用するペナルティを示しています。Chrome v31では割り当ての全体的な速度は2倍ですが、結果の文字列を使用しない場合ほど高速ではありません。


0

これは明らかにJavaScriptエンジンの実装に依存します。1つのエンジンのバージョンが異なる場合でも、著しく異なる結果が得られる可能性があります。これを確認するには、独自のベンチマークを行う必要があります。

String.concat最近のバージョンのV8の方がパフォーマンスが良いと思います。しかし、FirefoxとOperaにとってArray.joinは勝者です。


-1

私の推測では、すべてのバージョンが多くの連結のコストをかけていますが、結合バージョンはそれに加えて配列を構築しています。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.