JavaScript の文字列連結ベンチマーク

  • 投稿日:
  • by

JavaScript で文字列を繋げるのは += 演算子がベター、というのがもう 1 - 2 年ほど前から定石になっているらしい。

以前は「Array.push() で配列に格納して Array.join() で繋げる」ほうが圧倒的に処理速度が速かったんですが、最近のブラウザだと最適化が進んでパフォーマンスは逆転してるそうな。

JavaScript で、文字列の連結は "+=" が高速 - 地潜の日記
http://blog.livedoor.jp/jimuguri/archives/51347962.html

そういうことを google 検索で上位に来る上のブログで知ったんですけど、この人また他人の書いた記事を鵜呑みにしてて、なぜ配列を使うのが常識になっていたのかも知らないみたいなのでアテになりません。

なので実測。

s += "0" と var s = []; s.push("0") とを 100 - 5,100 回ほど繰り返す処理をそれぞれ 5,000 回ループさせた結果が下のような感じでした。いずれも Windows XP SP3 上でいくつかアプリ起動してる一般ユーザー的な環境で実行してます。
なんで連結の繰り返し数に幅があるのかっていうと、ここを固定にしてたらコンパイラが最適化したらしく同じような結果しか出てこず計測にならなかったので、5,000 回ループ中に 1 ずつ増える感じにしたからです。

せっかくなので、"0" 1 文字連結する場合と "000..." と 64 文字連結する場合の 2 種類を計ってみました。

Firefox 3.6.12

s += "0" 結果 : 761 ms  
s.push("0") 結果 : 2,120 ms

Chrome 9.0.587.dev

s += "0" 結果 : 144 ms  
s.push("0") 結果 : 411 ms

s += "000..." 結果 : 204 ms  
s.push("000...") 結果 : 1,507 ms

Safari 5.0.1 (7533.17.8)

s += "0" 結果 : 980 ms  
s.push("0") 結果 : 823 ms

s += "000..." 結果 : 964 ms  
s.push("000...") 結果 : 2,271 ms

IE 8.0.6001.18702

s += "0" 結果 : 6,109 ms  
s.push("0") 結果 : 8,047 ms

s += "000..." 結果 : 6,235 ms  
s.push("000...") 結果 : 10,999 ms

適当すぎるアレですけど、ここに使ったコード置いときますね。

Firefox で 64 文字版のテストはどうにも結果が返ってこず、ひどいときは Firefox が落ちてしまう場合もあったので計測不可ってことで。

結果から見て、どのブラウザでもおおむね s += "0" を使ったほうが速いって感じですね。
Safari は s.push() が 1 文字だとむしろ速いぐらいなのに 64 文字だと大幅に遅くなってて不思議。

JavaScript の文字列は名前だけ発祥元の Java を受け継いだのか immutable 不変オブジェクトになってます。
不変って文字のとおり、たとえば "1234" という文字列は一度メモリに割り当てられたら最後、二度と変化しないってことです。

変化しないので、"1234" + "0" という処理は「ふたつの文字列長を足した 5 文字分のメモリを確保して、そこにそれぞれコピーする」というわりと手間がかかるやり方でたいてい実行されてます。
s += "0" という命令文は、s と "0" を足した長さで確保したメモリにそれぞれをコピーした後のアドレスを s に持たせる、という感じになるわけです。

処理中はコピー元とコピー先が同時に存在することになるのでメモリ消費も一時的には倍になるという富豪的プログラミングな感じですが、毎回すべての文字列をコピーしまくるってのは実に効率の悪い処理になります。
なので、実装によっては最初から文字列の長さより多めのメモリを確保しつつその文字列の長さを別に持っておいて、連結の際は余ってるバッファにコピーしてから文字列の長さを足す、という感じになってる場合もあるんじゃないですかね。Java でいうところの StringBuilder のようなもので。

余ってるバッファだけでは足りない場合は結局メモリの再確保と全コピーという無駄な処理が必要になってしまいますけど、余らせるバッファ長をできるだけ賢く確保させるのが、開発者の腕に見せ所にもなるわけで、あるいはこの辺の調整が各ブラウザの速度差に関係してるかもしれません。