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系
    • A0 は、素の document にアクセスするやり方(教科書的)
    • A1 は、IE限定 で document へのアクセスを高速化するやり方(By id:amachang)
    • A2 は、ローカル変数に代入しておくやり方。A系で最も速い
    • A4 は、グローバル変数に代入しておくやり方。A2の次に速い
    • A5 は、window.cache._doc にキャッシュしておくやり方。A4の次に早い。
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系
    • B0 は、素の document にアクセス
    • B1 は、amachang
    • B2 は、ローカル変数に代入
    • B3 は、引数で document を渡す
    • B4 は、グローバル変数に代入
    • B5 は、window.cache._doc にキャッシュ
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
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系の全ソースは省略します。以下のリンクから直接コードを見てください。

基本的にはこんな感じです。

<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 だけは、それまでの経験や教科書的な思い込みはまるで通用せず。
    • だからこそ強く惹かれてるんかなぁ? と。