forEach色々とベンチマーク
cho45さんがCodeReposにあげていたJSEnumeratorに付随していたベンチマークでちょっと遊んでみました。
肝心なことを書き忘れていた。はっきりと差が出ているのは、10回のループをさらに1000回ループさせているからで、大抵の場合はどのメソッドを使っても体感できるほどの差は出ないと思います。
jQuery、prototype.js、MochiKit、fLDR、JSEnumeratorなどで使われているforEach関数の速度比較です。recursive eachは一応オリジナルです。
以下、実験ページ
http://ss-o.net/jsenumerator/benchmark/10.html
http://ss-o.net/jsenumerator/benchmark/100.html
結果
とりあえずWindowsのみmacは後で面倒だか、じゃなくて、windowsと大差ないみたいなので省略。
まだ集計途中なので、ほぼそのまま載せます。簡単に集計
100*1000 loop | WebKit-r30881(Win) | Firefox3 beta4 | Opera 9.50 beta(9807) | IE7 | いい加減な偏差値 |
---|---|---|---|---|---|
for | 17.099672 | 9.6295617 | 21.898841 | 51.4961 | 64.54258137 |
fLDR(mala) each | 34.399672 | 42.999289 | 49.998841 | 374.99052 | 57.7347206 |
recursive each (ベタ書き) | 60.899672 | 57.299289 | 46.798841 | 343.99052 | 55.62598365 |
MochiKit forEach | 53.099672 | 71.399289 | 114.098841 | 405.99052 | 53.31218988 |
prototype.js Array#each | 62.499672 | 108.996562 | 99.998841 | 749.99052 | 47.54804145 |
recursive each | 109.999161 | 114.997037 | 81.199344 | 718.98898 | 44.78568234 |
jQuery each | 60.899672 | 150.996562 | 342.990381 | 593.99052 | 41.78387405 |
JSEnumerator each | 139.999161 | 161.996996 | 296.990381 | 811.99058 | 34.66692666 |
ブラウザごとの平均 | 67.36204425 | 89.78932321 | 131.7467889 | 506.4285325 |
集計前のデータ:http://ss-o.net/jsenumerator/benchmark/jseach_data.tsv
ブラウザ対決では、WebKit Nightlyが期待通りの最速。Firefox2がFirefox3より速くて、ちょっと微妙。体感的にはFirefox3のほうが速いので、どこかおかしいのかも。拡張はどちらもGreasemonkey+Firebugくらいしか入ってない(Fx3のほうが少ない)んだけどなぁ。(Opera9.26はばらつきが大きくてあまり当てにならない、9.5は安定してやっぱり9.5もあんまり安定してないけど高速だった)
関数対決は、malaさんのforeach関数(コメント欄でid:malaさんに元ネタを教えていただきました。元記事は消えてますが、id:brazilさんのブクマコメに概要がありました)が最速(上に載せた一覧は短いループなのでrecursiveのほうが良さそうですが、100のほうだと明らかにmalaさんのが高速です)。これはレベルが違います。オープンソースのfastladderから、
http://fastladder.googlecode.com/svn/trunk/fastladder/public/js/common.js の 240行目より、
function foreach(a,f){ var c = 0; var len = a.length; var i = len % 8; if (i>0) do { f(a[c],c++,a); } while (--i); i = parseInt(len >> 3); if (i>0) do { f(a[c],c++,a);f(a[c],c++,a); f(a[c],c++,a);f(a[c],c++,a); f(a[c],c++,a);f(a[c],c++,a); f(a[c],c++,a);f(a[c],c++,a); } while (--i); };
これ、始めて見たときは笑いました。まず、8で割った余りの端数をwhileループで片付け、残った値を8個ずつ一気に処理しています。(len >> 3はlenを8(2^3)で割ったときの整数を返しています)
ちなみに、オリジナルの再帰処理ループもなかなか健闘してくれました。再帰なので巨大な配列には使えないですが、なんといってもライブラリなど一切不要、数行で書ける手軽さが強みです。再帰呼び出しにsetTimeoutを挟んであげれば一瞬でサクサクなアニメーションを書くこともできます。
,"recursive each" : function () { var list = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]; for (var i = 0; i < LOOP; i++) { var j=0,l=list.length; var f = function(a,j) { a; j; if (++j < l) f(list[j],j); }; f(list[j],j); } }
べた書きなのはチートくさいかなと思い、関数を用意したパターンも試してみました。
function recursive_each(a,f){ var l=a.length; var _f = function(v,j) { f(v,j,a); if (++j < l) _f(a[j],j); }; _f(a[0],0); };
関数呼び出しが倍増したので、速度もほぼ2倍に!! 普通のループですね。
この機会に各ライブラリの関数を比べてみたわけですが、各each関数が似ているようでかなり違うことに気が付きます。
jQueryは第一引数に配列のインデックスが入ってきます。これはjQueryだけの特徴で、知らないと嵌ります(すぐ気が付くけど)。MochiKit(とJSEnumerator)は配列のインデックスを渡してくれません。なので、カウントアップしたいときは外側にインデックス用の変数を用意する必要があるみたいです(これは私が知らないだけで、別の解決策があるのかも)。