期間限定、Google Chrome 2 の HiResTimeでベンチマーク
dev版のGoogle Chrome 2.0.166.1を弄っていたら、HiResTimeというグローバル関数を見つけました。perlとかでお馴染みの高精度のタイマー関数のようです。
さっそく試してみましたが、非常に高性能でお薦めです……と言いたいところなのですが、実はChromiumではすでに廃止されてしまっているようです。
hide the native HiResTime() function.
http://codereview.chromium.org/28201
というわけで、期間限定の機能です。この機会に是非お試しを(dev版についてはChrome Release Channels - The Chromium Projectsで)。
ちなみに、Chromiumといえば、GM_xmlhttpRequestのコミットが間近です。
Issue 27037: Implement GM_xmlhttpRequest -
Code Review
こっちも楽しみ。
さて、前書きで腰を折ってしまった感はありますが、さっそく地味なベンチを取っていきます。
まずは、Math.maxを一回だけ実行した時間を計ってみます。
javascript:(function(){var t = HiResTime(); Math.max(1,2); alert(HiResTime() - t);})();
結果は、0.00001239776611328125 と出ました。こんな感じでワンステップだけで計ることができます。
なお、HiResTime()を2回呼ぶ時間を計測できます。これを誤差として考慮すればより正確なベンチマークになりそうです。
javascript:(function(){var t = HiResTime();alert([HiResTime() - t,HiResTime()-HiResTime()].join('\n'));})();
続いて、Math.maxと、ベタな大小を比較をそれぞれ実行した結果を比べて見ます(普通の表記と指数表記が混ざると分かり難いので、指数表記にならないようにしておきます((書き終わって思ったけど、全部指数表記にすれば良かった… あとでやる気が出たらやるかも)))。直した
Thx!mayokara!
JavaScript、数値の指数表記/小数表記変換にはNumber#toExponential/Number#toPrecisionが使える
http://mayokara.info/deadspace/?029#d20090228
javascript:(function(){ var one = 1, two = 2; function MathMax(){ var t =HiResTime(); var r = Math.max(one,two); return HiResTime()-t; }; function max2(){ var t =HiResTime(); var r = one > two ? one : two; return HiResTime()-t; }; alert([MathMax().toExponential(3), max2().toExponential(3) ].join('\n')); })();
3.576e-6 9.537e-7
と、Math.maxが比較的高コストだというのがはっきり分かると思います。
折角なので、もう少ししっかりテストしてみます。
http://ss-o.net/test/hires.html
それぞれ10回計測し、トータルではHiResTimeの実行誤差を割引いています。
0 | _HiResTime | MathMax | max2 |
1 | 1.192e-6 | 4.292e-6 | 9.537e-7 |
2 | 9.537e-7 | 5.007e-6 | 9.537e-7 |
3 | 4.768e-7 | 7.153e-7 | 4.768e-7 |
4 | 4.768e-7 | 7.153e-7 | 4.768e-7 |
5 | 2.384e-7 | 4.768e-7 | 4.768e-7 |
6 | 4.768e-7 | 4.768e-7 | 4.768e-7 |
7 | 4.768e-7 | 4.768e-7 | 2.384e-7 |
8 | 2.384e-7 | 4.768e-7 | 4.768e-7 |
9 | 4.768e-7 | 4.768e-7 | 4.768e-7 |
10 | 4.768e-7 | 4.768e-7 | 4.768e-7 |
total | 5.484e-6 | 8.106e-6 | -3.727e-10 |
こうしてみると初回のみMath.maxが遅いものの、(おそらくJITの効果で)何度も実行する場合は差がなくなることが分かります。
HiResTimeを2回呼ぶだけの処理と同じ時間になっているので、このレベルはJITの効果を強く受けていると考えられます。
これはV8のJIT特有である可能性が高い*1ので、注意が必要です。
続いて、数字を数値に直す処理を計測してみます。
まず、関数はこんな感じです。
function _Number(){ var t =HiResTime(); var r = Number(ONE); return HiResTime()-t; } function _0(){ var t =HiResTime(); var r = ONE-0; return HiResTime()-t; } function _x1(){ var t =HiResTime(); var r = ONE*1; return HiResTime()-t; } function _float(){ var t =HiResTime(); var r = parseFloat(ONE); return HiResTime()-t; }
結果はこんな感じです。
0 | _HiResTime | _Number | _0 | _x1 | _float |
1 | 7.153e-7 | 3.338e-6 | 1.431e-6 | 2.861e-6 | 3.099e-6 |
2 | 4.768e-7 | 4.053e-6 | 1.192e-6 | 2.146e-6 | 3.815e-6 |
3 | 4.768e-7 | 9.537e-7 | 9.537e-7 | 7.153e-7 | 7.153e-7 |
4 | 4.768e-7 | 7.153e-7 | 7.153e-7 | 7.153e-7 | 4.768e-7 |
5 | 4.768e-7 | 7.153e-7 | 4.768e-7 | 7.153e-7 | 9.537e-7 |
6 | 4.768e-7 | 4.768e-7 | 7.153e-7 | 7.153e-7 | 4.768e-7 |
7 | 2.384e-7 | 7.153e-7 | 7.153e-7 | 7.153e-7 | 7.153e-7 |
8 | 4.768e-7 | 7.153e-7 | 7.153e-7 | 7.153e-7 | 7.153e-7 |
9 | 4.768e-7 | 7.153e-7 | 7.153e-7 | 4.768e-7 | 4.768e-7 |
10 | 4.768e-7 | 7.153e-7 | 7.153e-7 | 7.153e-7 | 4.768e-7 |
total | 4.768e-6 | 8.345e-6 | 3.577e-6 | 5.722e-6 | 7.153e-6 |
Number、parseFloat、*1 は初回コストが高めで、-0は比較的高速なようです。*1は-0で差が出るのには少々驚きです(が、誤差の範囲なのかも)。こちらも、2回目以降はだんだんと差がなくなります。
最後に、日付のシリアル値を取得する方法を比べてみます。
function date_0(){ var t =HiResTime(); var r = new Date-0; return HiResTime()-t; } function date_x1(){ var t =HiResTime(); var r = new Date*1; return HiResTime()-t; } function date_plus(){ var t =HiResTime(); var r = +new Date; return HiResTime()-t; }
0 | _HiResTime | date_plus | date_0 | date_x1 |
1 | 7.153e-7 | 4.277e-4 | 3.338e-6 | 3.576e-6 |
2 | 4.768e-7 | 1.144e-5 | 1.907e-6 | 2.861e-6 |
3 | 4.768e-7 | 1.431e-6 | 7.153e-7 | 7.153e-7 |
4 | 2.384e-7 | 9.537e-7 | 7.153e-7 | 7.153e-7 |
5 | 4.768e-7 | 9.537e-7 | 7.153e-7 | 7.153e-7 |
6 | 4.768e-7 | 7.153e-7 | 7.153e-7 | 7.153e-7 |
7 | 4.768e-7 | 7.153e-7 | 7.153e-7 | 9.537e-7 |
8 | 2.384e-7 | 7.153e-7 | 1.192e-6 | 9.537e-7 |
9 | 4.768e-7 | 9.537e-7 | 7.153e-7 | 7.153e-7 |
10 | 4.768e-7 | 7.153e-7 | 7.153e-7 | 7.153e-7 |
total | 4.530e-6 | 4.418e-4 | 6.914e-6 | 8.106e-6 |
最初の実行だけ大分時間がかかっています。
試しに順番を変えてみます。
0 | _HiResTime | date_0 | date_x1 | date_plus |
1 | 7.153e-7 | 2.575e-4 | 3.576e-6 | 2.623e-6 |
2 | 4.768e-7 | 9.298e-6 | 2.623e-6 | 2.146e-6 |
3 | 4.768e-7 | 1.669e-6 | 9.537e-7 | 9.537e-7 |
4 | 4.768e-7 | 9.537e-7 | 7.153e-7 | 7.153e-7 |
5 | 2.384e-7 | 7.153e-7 | 7.153e-7 | 9.537e-7 |
6 | 4.768e-7 | 9.537e-7 | 7.153e-7 | 7.153e-7 |
7 | 4.768e-7 | 9.537e-7 | 9.537e-7 | 7.153e-7 |
8 | 4.768e-7 | 7.153e-7 | 7.153e-7 | 7.153e-7 |
9 | 2.384e-7 | 7.153e-7 | 7.153e-7 | 7.153e-7 |
10 | 4.768e-7 | 9.537e-7 | 7.153e-7 | 7.153e-7 |
total | 4.530e-6 | 2.699e-4 | 7.868e-6 | 6.437e-6 |
やはり初回のみ時間がかかっています。
と、勘の良い方はすでに気がついているかと思いますが、どうやらnew Dateの初回呼び出しがボトルネックではないかと考えられます。
そこで、(new Date()を呼び出しておく意味を兼ねて) もう一度計り直してみます。
0 | _HiResTime | date_0 | date_x1 | date_plus |
1 | 7.153e-7 | 1.907e-6 | 1.431e-6 | 1.192e-6 |
2 | 2.384e-7 | 1.192e-6 | 7.153e-7 | 7.153e-7 |
3 | 4.768e-7 | 7.153e-7 | 7.153e-7 | 7.153e-7 |
4 | 4.768e-7 | 7.153e-7 | 7.153e-7 | 7.153e-7 |
5 | 4.768e-7 | 7.153e-7 | 7.153e-7 | 7.153e-7 |
6 | 2.384e-7 | 9.537e-7 | 7.153e-7 | 7.153e-7 |
7 | 4.768e-7 | 7.153e-7 | 9.537e-7 | 7.153e-7 |
8 | 4.768e-7 | 7.153e-7 | 7.153e-7 | 7.153e-7 |
9 | 2.384e-7 | 7.153e-7 | 7.153e-7 | 7.153e-7 |
10 | 4.768e-7 | 9.537e-7 | 7.153e-7 | 7.153e-7 |
total | 4.292e-6 | 5.006e-6 | 3.814e-6 | 3.337e-6 |
予想通りの結果です。かなり正確な計測ができたようです。
と、割とマニアックなベンチマークを取ってみましたが、結局の所 V8エンジンの実装に依存しているので、あまり真に受けず、そういった挙動をするエンジンがあると憶えておくくらいが良いと思います。
お茶を濁したところで今回はここまで。
*1:元々、この記事の内容はすべてGoogle Chrome特有の話ですが…