JavaScript の文字列の結合のパフォーマンスを比較してみました

2012 年に業務で初めて ActionScript を使っていたときに、文字列を Array.push でまとめて Array.join で結合した結果を取ってきていたのですが、パフォーマンスが気になったので比較してみました。

環境

  • Node.js: v8.9.4

1 万回

次のようなコードを書きました。

// string.js
(() => {
  // String +=
  let s = ''
  console.time('timer1(String)')
  for (let i = 0; i <= 10000; i++) {
    s += 'a'
  }
  console.timeEnd('timer1(String)')
  // console.log(`s: ${s}`)

  // Array.join
  const a = []
  console.time('timer2(Array)')
  for (let i = 0; i <= 10000; i++) {
    a.push('a')
  }
  console.timeEnd('timer2(Array)')
  // console.log(`a: ${a.join('')}`)
})()

文字列を String の += の演算子で結合するパターンと、 Array.push で結合するパターンで、 1 文字を 1 万回結合する時間を計測しています。 文字列を出力するとコンソールが埋まってしまって見づらいので、コメントアウトしてしまいました。

3 回実行してみました。

$ for number in {0..2}; do node string.js; done
timer1(String): 0.307ms
timer2(Array): 1.582ms
timer1(String): 0.301ms
timer2(Array): 1.609ms
timer1(String): 0.282ms
timer2(Array): 1.574ms

やっぱり String の += の演算子を使った方が速いみたいでした。

10 万回

10 万回結合を繰り返すパターンも 3 回実行してみました。

$ for number in {0..2}; do node string.js; done
timer1(String): 12.974ms
timer2(Array): 4.503ms
timer1(String): 12.598ms
timer2(Array): 4.626ms
timer1(String): 12.843ms
timer2(Array): 5.221ms

あれ、 Array の方が速くなってしまっています。

100 万回

100 万回結合を繰り返すパターンも 3 回実行してみました。

$ for number in {0..2}; do node string.js; done
timer1(String): 128.542ms
timer2(Array): 50.332ms
timer1(String): 131.735ms
timer2(Array): 45.633ms
timer1(String): 135.019ms
timer2(Array): 42.879ms

こちらも Array の方が速くなってしまっています。

整理

10 万回と 100 万回の結果について、 String は Array の 3 倍程度遅い結果になりました。

Array.join のコスト

結果を得るコストも含めないと String の += 演算子に不利かと思いまして、次のように修正しました。 Array の方は Array.join で純粋な文字列にするコストを含めました。

// string.js
(() => {
  // String +=
  let s = ''
  console.time('timer1(String)')
  for (let i = 0; i <= 10000; i++) {
    s += 'a'
  }
  console.timeEnd('timer1(String)')
  // console.log(`s: ${s}`)

  // Array.join
  const a = []
  console.time('timer2(Array)')
  for (let i = 0; i <= 10000; i++) {
    a.push('a')
  }
  const aa = a.join('') // ← Array.join で結合
  console.timeEnd('timer2(Array)')
  // console.log(`aa: ${aa}`)
})()

100 万回結合を繰り返すパターンを 3 回実行してみました。

$ for number in {0..2}; do node string.js; done
timer1(String): 131.149ms
timer2(Array): 107.956ms
timer1(String): 132.549ms
timer2(Array): 110.209ms
timer1(String): 127.809ms
timer2(Array): 110.966ms

String を使っても無視できるくらいの差まで縮まったでしょうか。 それでもわずかに Array の方が速いみたいでした。

100 万回も結合することないかな。

終わり

なんで Array.push を使っていたのか、思い出していたのですが、おそらく 2000 年代後半にそのような記事を見たことがあったからだと思います。

最近、参考に挙げた本を読み返してみたのですが、 IE7 までは String の += 演算子が遅くて、 Array.push を使った方が速かったようです。 調べてみると、 IE7 のリリースが 2006 年で、 IE8 のリリースが 2009 年です。 もう 10 年も前の話でした。

31 March 2018 追記

参考の書籍について、誤りがありました。 文字列の結合についての記載があったのは、次の書籍でした。

5.1.1 プラス演算子とプラスイコール演算子

プラス演算子 (+) とプラスイコール演算子 (+=) は、文字列を結合するための最も単純な手法である。

そして、 IE7 とそれ以前を除くすべてのブラウザにおいて、ほかの選択肢を考える必要がないほどに、この手法はよく最適化されている。

ハイパフォーマンスJavaScript | Nicholas C. Zakas, 水野 貴明 |本 | 通販 | Amazon

遅いとは記載されていませんが、 IE7 以前だと +, += が最適化されていないと受け取れます。

手法は、次のようなものが記載されていました。

  • 5.1.1 プラス演算子とプラスイコール演算子
  • 5.1.2 配列の連結
  • 5.1.3 String.prototype.concat

これらの手法は、短い文字列をあちこちに結合するような処理ではいずれも十分に高速だ。したがって、ちょっとした処理にすぎないのであれば、最も使いやすいものを選んで使えばよいだろう。

しかし結合する文字列の数やそれぞれの長さが大きくなった場合には、この中のいくつかの手法の長所が見え始める。

ハイパフォーマンスJavaScript | Nicholas C. Zakas, 水野 貴明 |本 | 通販 | Amazon

ちょっとした処理であればあまり変わらないようです。

参考