amachang の 「一行で IE の JavaScript を高速化する方法」を掘り下げてみた
2009-11-12 ナビ子記法について追記しました
本文
今日は、amachangさんの記事 http://d.hatena.ne.jp/amachang/20071010/1192012056 を 1mm だけ掘り下げ、IE 以外のブラウザでも document へのアクセスを速くする方法がないか、色々試してみます。
# 記事自体はずいぶん前に書き上げてたけど、公開するの忘れてたんだな。
C系を追加しました。C系は「ネストしたスコープからグローバル変数にアクセスするとどうなるか?」がテーマです。
試したこと
以下は様々な方法で document へのアクセス速度を計測します。
A系では、非日常的な方法で測定し、B系では実際の用法に近い形で測定します。C系では何重にもネストしたスコープから、グローバル変数にアクセスするとどうなるかを測定します。
- A系
Browser | A0 | A1 | A2 | A4 | A5 |
IE6 | 3422 | 454 | 344 | 438 | 609 |
Firefox3.1(JIT) | 357 | - | 4 | 4 | 11 |
Firefox3.0 | 334 | - | 156 | 184 | 237 |
Firefox2.0 | 469 | - | 219 | 485 | 531 |
Safari3 | 313 | - | 125 | 313 | 313 |
Chrome(JIT) | 215 | - | 8 | 21 | 20 |
Opera9.61 | 1719 | - | 219 | 282 | 328 |
Opera10α | 688 | - | 234 | 297 | 344 |
- B系
Browser | B0 | B1 | B2 | B3 | B4 | B5 |
IE6 | 5562 | 2234 | 5780 | 2125 | 2204 | 2375 |
Firefox3.1(JIT) | 604 | - | 720 | 4 | 5 | 13 |
Firefox3.0 | 527 | - | 560 | 312 | 374 | 383 |
Firefox2.0 | 984 | - | 1078 | 735 | 984 | 1047 |
Safari3 | 813 | - | 907 | 657 | 781 | 844 |
Chrome(JIT) | 217 | - | 235 | 20 | 29 | 38 |
Opera9.61 | 2062 | - | 2109 | 547 | 593 | 625 |
Opera10α | 985 | - | 1000 | 578 | 610 | 656 |
- C系
Browser | C0 | C1 | C3 | C4 | C5 |
IE6 | 8130 | 4530 | 4380 | 4540 | 4680 |
Firefox3.1(JIT) | 1006 | - | 31 | 34 | 46 |
Firefox3.0 | 1044 | - | 637 | 637 | 696 |
Firefox2.0 | 1672 | - | 1468 | 1477 | 1765 |
Safari3 | 1515 | - | 1375 | 1501 | 1546 |
Chrome(JIT) | 272 | - | 67 | 74 | 88 |
Opera9.61 | 2515 | - | 1156 | 1140 | 1172 |
Opera10α | 1570 | - | 1141 | 1172 | 1209 |
こうすると、いいじゃないかな
A2 と B3 が一番速いんですが、いまさら書き直すのは大変ですよね。そこで B4 をベースにした、こんな方法はどうでしょう。
script の先頭に、以下の1行を仕込んでおきましょう。
var _doc = document;
そして、document の代わりに _doc を使いましょう。
function hoge() { _doc.piyo(); }
全ての JavaScript ファイルを grep して、 "document." を "_doc." に置換してしまいましょう。
# doc って名前にすると、grep で引っかかりすぎるので、アンダーバーとかつけとくと吉。
# 名前はお好みで。グローバル変数の汚染が嫌なら、B5 をベースにしても実速度的には大差ありませんし。
2009-11-12 追記 またはこういう方法(ナビ子記法)も
(function(win, doc) { var div = doc.createElement("div"); })(window, document);
以下で実際に試せます。
A0 素の document にアクセスする
<html><head><title>A0</title></head><body><script> window.onload = function() { var begin = +new Date; job(1000000); alert(+new Date - begin); } function job(times) { for (var i = 0; i < times; ++i) { document; // ここ } } </script></body></html>
A1 amachang式
<html><head><title>A1</title></head><body><script> // see http://d.hatena.ne.jp/amachang/20071010/1192012056 /*@cc_on _d=document;eval('var document=_d')@*/ // ここ window.onload = function() { var begin = +new Date; job(1000000); alert(+new Date - begin); } function job(times) { for (var i = 0; i < times; ++i) { document; // ここ } } </script></body></html>
A2 ローカル変数に代入
<html><head><title>A2</title></head><body><script> window.onload = function() { var begin = +new Date; job(1000000); alert(+new Date - begin); } function job(times) { var _doc = document; // ここ for (var i = 0; i < times; ++i) { _doc; // ここ } } </script></body></html>
A4 グローバル変数に代入
<html><head><title>A4</title></head><body><script> var _doc = document; // ここ window.onload = function() { var begin = +new Date; job(1000000); alert(+new Date - begin); } function job(times) { for (var i = 0; i < times; ++i) { _doc; // ここ } } </script></body></html>
A5 window.cache._doc にキャッシュ
<html><head><title>A5</title></head><body><script> var cache = { _doc: document }; // ここ window.onload = function() { var begin = +new Date; job(1000000); alert(+new Date - begin); } function job(times) { for (var i = 0; i < times; ++i) { cache._doc; // ここ } } </script></body></html>
B0 素の document にアクセス
<html><head><title>B0</title></head><body><script> window.onload = function() { var begin = +new Date; job(1000000); alert(+new Date - begin); } function job(times) { for (var i = 0; i < times; ++i) { loop(); } } function loop() { document; // ここ } </script></body></html>
B1 amachang式
<html><head><title>B1</title></head><body><script> // see http://d.hatena.ne.jp/amachang/20071010/1192012056 /*@cc_on _d=document;eval('var document=_d')@*/ // ここ window.onload = function() { var begin = +new Date; job(1000000); alert(+new Date - begin); } function job(times) { for (var i = 0; i < times; ++i) { loop(); } } function loop() { document; // ここ } </script></body></html>
B2 ローカル変数に代入
<html><head><title>B2</title></head><body><script> window.onload = function() { var begin = +new Date; job(1000000); alert(+new Date - begin); } function job(times) { for (var i = 0; i < times; ++i) { loop(); } } function loop() { var _doc = document; // ここ _doc; // ここ } </script></body></html>
B3 引数で document を渡す
<html><head><title>B3</title></head><body><script> window.onload = function() { var begin = +new Date; job(1000000); alert(+new Date - begin); } function job(times) { var _doc = document; for (var i = 0; i < times; ++i) { loop(_doc); // ここ } } function loop(_doc) { // ここ _doc; // ここ } </script></body></html>
B4 グローバル変数に代入
<html><head><title>B4</title></head><body><script> var _doc = document; // ここ window.onload = function() { var begin = +new Date; job(1000000); alert(+new Date - begin); } function job(times) { for (var i = 0; i < times; ++i) { loop(); } } function loop() { _doc; // ここ } </script></body></html>
B5 window.cache._doc にキャッシュ
<html><head><title>B5</title></head><body><script> var cache = { _doc: document }; // ここ window.onload = function() { var begin = +new Date; job(1000000); alert(+new Date - begin); } function job(times) { for (var i = 0; i < times; ++i) { loop(); } } function loop() { cache._doc; // ここ } </script></body></html>
C系
C系の全ソースは省略します。以下のリンクから直接コードを見てください。
- C0 は、素の document にアクセス
- C1 は、amachang式
- C3 は、引数で document を渡す
- C4 は、グローバル変数に代入
- C5 は、window.cache._doc にキャッシュ
基本的にはこんな感じです。
<html><head><title>C0</title></head><body><script> // var _doc = document; // こことか window.onload = function() { var begin = +new Date; job(1000000); alert(+new Date - begin); } function job(times) { for (var i = 0; i < times; ++i) { loop(); } } function loop() { nest1.nest2.nest3.nest4.nest5(); // こことか } var nest1 = { nest2: { nest3: { nest4: { nest5: function() { document; // こことか } } } } }; </script></body></html>
やってみたけど速くなんない? それは残念。
uupaa.jsuuQuery.js の CSSセレクタ が速いのは、こういった地味〜な高速化を随所に施しているからです。
nth-child や *-type-of 等は、普通に1万回ループしてたりするので、小さな積み重ねでも効果があります。
反省会
- 開発速度が遅いのは、こんな細かいベンチをいちいち取ってたりするからって話もある。
- 来年からは、素の document ではなく var _doc = document; 使いましょう。
- それこそ様々な言語や環境で仕事をしたけど、ブラウザ + JavaScript だけは、それまでの経験や教科書的な思い込みはまるで通用せず。
- だからこそ強く惹かれてるんかなぁ? と。