配列処理いろいろ
javascriptで配列を操作する場合,素直にfor文を使う方法や,map関数,each関数を使う方法,はたまたjQueryを使う方法などいろいろな書き方が考えられます.
それらの実行速度については,いろいろな記事に解説が載っていますが,それぞれのバージョンによってどれが速いのかがだいぶ変わってくるようですし,情報がバラバラとあるので比較が大変です.今回は,配列操作周りで気になっている速度比較をまとめておこなってみます.
概要
- 実施日:2016/05/16
- 実行PC:MacBook Pro 16 GB 1600 MHz DDR3
- 実行OS:OSX 10.10
- 対象ブラウザ:Chrome50.0, Firefox46.0, Safari 8.0.5
- JSLitmusを使用して計測.
- 単位は"operations per second(秒間実行回数)"なので,値が大きいほど計算速度が速い.
配列初期化・追加部門
処理
新しく配列を作り,0,1,2と順に頭から数値をいれていく.
比較
- A: a=[] で初期化 a[i]で追加
var arr = [];
for (var i=0; i<cnt; i++){
arr[i] = i;}
- B: a=[] で初期化 pushで代入
var arr = [];
for (var i=0; i<cnt; i++){
arr.push(i);}
- C: new Arrayで初期化
var arr = new Array(cnt);
for (var i=0; i<cnt; i++){
arr[i] = i;}
結果
左からChrome, Firefox, Safari
Safari,Firefoxではあまり結果は変わらず,Chromeでは C:new Array()
で初期化するのが圧倒的に速いという結果に.他と比べても頭一つ抜けていますね.参考にしたサイトでは,昔はnew Array()のほうが速かったが,new Array()と[]でほぼ差がない,むしろ[]のほうが速いとの情報も載っていたので,意外な結果でした.
配列から新しい配列を作成する部門
処理1
0~10000, 0~100000, 0~100000が一つずつ順番に入った配列の中身を,それぞれ二乗した新しい配列を作る
比較
- A: forループ
var result = new Array(cnt);
for (var i=0; i<cnt; i++){
result[i] = arr[i] * arr[i];
}
- B: for...in
var result = new Array(cnt);
for (var i in arr){
result[i] = arr[i] * arr[i];
}
- C: forEach
var result = new Array(cnt);
arr.forEach(function(el, i){ result[i] = el*el;});
- D: array.map
var result = arr.map(function(el){ return el*el;});
- E: jQuery.each
var result = new Array(cnt);
$.each(arr, function(i, el){ result[i] = el*el;});
- F: jQuery.map
var result = $.map(arr, function(el){ return el*el;});
結果
左からChrome, Firefox, Safari
- 0~10000
- 0~100000
- 0~1000000
chromeについてはどの配列サイズでもforループが最速.それ以外だとjQuery.eachが速いという結果に.nativeのforEachより速いんですね.
またFirefoxだとforループとnativeのforEach, mapが速いという結果に.
Safariではfor...inのぞいてそこまで大きく差はでないものの,nativeのmap, jQueryのeachが速そうでした.
どのブラウザでもfor...inがダントツで遅く,jQuery.mapは配列の大きさが大きすぎるとRangeError: Maximum call stack size exceeded.
で落ちてしまうという結果になりました.
処理2
0~10000, 0~100000, 0~100000が一つずつ順番に入った配列の中身から,3で割り切れるものだけを配列化する.
比較
- A: forループ
var result = []
var j = 0;
for (var i=0; i<cnt; i++){
if (arr[i]%3==0){
result[j] = arr[i];
j++;}
}
- B: filter
var result = arr.filter(function(el){ return el%3==0; });
}
- C: jQuery.grep
var result = jQuery.grep(arr, function(el){ return el%3==0;});
結果
左からChrome, Firefox, Safari
- 0~10000
- 0~100000
- 0~1000000
chromeについてはどの配列サイズでもforループが最速.それ以外だとjQuery.grepが速いという結果に.
またFirefoxだとどれも似たり寄ったりで,数が少ないうちは若干forループが早く,配列のサイズが大きくなるとjQuery.grepが他と比べて少し速い印象.
SafariではjQuery.grepが速そうでした.
配列全体を計算して数値を出す部門
処理
0~100000が一つずつ順番に入った配列の中身を,全て足し合わせる.
比較
- A: forループ
var sum = 0;
for (var i=0; i<cnt; i++){
sum += arr[i];
}
- B: reduce
var sum = arr.reduce(function(a, b){ return a + b; })
- C: jQuery.each
var sum = 0;
jQuery.each(arr, function(el){ sum+el; });
結果
左からChrome, Firefox, Safari
どのブラウザでもforループが最速,reduce, jQuery.each勝負はChrome, SafariではjQuery.eachに軍配があがる結果に.
結論 単純に速度を出したいだけならforループ安定
速度を出したいならforループ安定.Chrome以外ならjQuery.each, map関数もそこそこ活躍できそう,という結果になりました(for...inはあまりに遅かったので最初しか試してません).for文以外では,イテレーションのたびに毎回関数呼び出しがかかるから遅い?のかな?
ただし今回の結果はあくまで一回のループ処理が単純かつ短時間で済むものでの結果なので,そこが複雑になってきたときにどうなるかはまた別の話です(単純にそれぞれの差が縮まるんでしょう).が,単純タスクを大量にループ処理したい場合は参考になるかと思います.
また,forループを使わない場合でも,nativeのmap, each等よりjQuery.eachが速いのは意外でした.
特にChromeではforループが他と比べて爆速ですが,注意すべき点としてあらかじめnew Array()で配列を確保しておかないと速度がガタ落ちしてしまう点が挙げられます.
上の例では,先に挙げた元の配列の二乗が入った配列を返す処理を,new Array()で配列を確保してからforループで処理したもの,[]で配列を作り,逐次追加したもの,jQuery.eachで処理したもの(配列サイズは1000000)になります.ご覧のように[]で配列を作った場合は速度が急落し,jQuery.eachよりも遅い結果となってしまいます.
短くコードを書ける点や可読性と,速度に関してはまた別の問題なので,処理内容に応じて適切な書き方をしていきたいですね.
UnderscoreやLo-Dash等の_.eachや_.map等も,調べ次第追記していきたいと思います.