JavaScript の高速化その2 「全てを疑い、自分の目で確認すること」

こういう泥臭い資料作りもやってるので、一応書き残します。

問題1. array.length へのアクセス

var ary = new Array(100000); な配列があるとします。

IE6環境下で、配列の長さを求める方法を、早い順に並べてください。

TEST1. a.length;
TEST2. var L = "length"; a[L];
TEST3. a["length]";

正解は、TEST1 < TEST3 < TEST2 です。

Browser TEST1 TEST2 TEST3 総評
Chrome 1 1 1 全て同じスコア
Safari 1 2 3 TEST2はTEST1の2倍, TEST3に至っては3.4倍スコアが違う
Opera9.27 1 1 1 全て同じスコア
Opera9.6β 1 2 2 1.3倍スコアが違う
Firefox2 1 2 1 TEST1とTEST2で1.5倍違う
Firefox3 2 1 2 TEST2が12%早い
IE6 1 3 2
IE8β2 1 2 1 TEST1とTEST2で1.2倍違う

数値は順位


配列の要素数が欲しければ、 a.length を使いましょう。

問題2. document.getElementById() へのアクセス

<div id="root"></div>

IE6環境下で、この要素を検索する方法を、早い順に並べてください。

TEST10. document.getElementById("root");
TEST11. var ID = "getElementById"; document[ID]("root");
TEST12. var _doc = document; _doc.getElementById("root");
TEST13. var _doc = document, ID = "getElementById"; _doc[ID]("root");

正解は、TEST12 < TEST13 < TEST10 < TEST11 です。TEST12とTEST13の差は0.0058%0.58%です。
TEST12とTEST11では5.6%の差が見られます。

Browser TEST10 TEST11 TEST12 TEST13 総評
Chrome 3 3 1 2 微妙にTEST12が早い
Safari 2 4 1 3 TEST10とTEST11では2倍スコアが違う
Opera9.27 3 4 1 1 TEST12とTEST11では2倍スコアが違う
Opera9.6β 3 4 1 2 TEST12とTEST11では1.8倍スコアが違う
Firefox2 3 4 1 2 TEST11とTEST12では1.3倍スコアが違う
Firefox3 2 4 1 3 TEST11とTEST12では2.1倍スコアが違う
IE6 3 4 1 2
IE8β2 4 4 1 2 TEST11とTEST12では1.6倍スコアが違う

数値は順位

idで要素を検索したければ、var _doc = document; _doc.getElementById("root"); を使いましょう。

問題3. ネストされたプロパティへのアクセス

これと
  function hoge() {
    var ary = Array(10000);
    return Array.prototype.slice.call(ary);
  }
これでは?
  function hoge() {
    var ary = Array(10000), slice = Array.prototype.slice;
    return slice.call(ary);
  }

IE6では後者が7%ほど早くなります。Chromeでも1%ほど高速化されます。

結論

lengthプロパティにアクセスする場合は、小細工はしないほうが良く、
ネストされたプロパティにアクセスする場合は、ネストを浅くするためにキャッシュしておいたほうが良く、
documentにアクセスする場合には、documentをローカル変数にキャッシュしたほうが速度が向上します(ローカルスコープの中にですよ)。

つまり

var _doc = document; // これだと、いまいち効果が薄いので、
function hoge() {
  var e = _doc.getElementById("piyo");
}
function hoge() {
  var _doc = document; // 面倒でもローカルスコープ内で宣言する必要がある。
  var e = _doc.getElementById("piyo");
}

プロパティによっては特別扱いされているものもあるかもしれませんが、原則こんな感じでいけます。

ほかには、

var i = 0, sz = elm.childNodes.length;
for (; i < sz; ++i) { elm.childNodes[i] }

よりも

var c = elm.firstChild;
for (; c; c = c.nextSibling) { c }

が、60倍以上高速だったりします(in IE6)。

あとは、Papervision3DJavaScriptに移植しようとたくらみ、コードリードしていた時に、こんな感じの(関数の引数をわざわざローカル変数にマップしている)コードを沢山見かけました。

function(aa, bb, cc) {
  var a = aa, b = bb, c = cc;
  return a * b * c;
}

高速化目的なのかな? と思い同様のコードをJavaScriptで試してみたら、いずれのブラウザでも期待を裏切る結果になりました。
もしかして、上記の書き方をすると、ActionScript では高速化するのかもしれません。


興味がある方はいろいろと計測してみると良いでしょう。
# 良かったら結果も教えてください。

id:amachang が以前 http://d.hatena.ne.jp/amachang/20071010/1192012056IE + document について言及していましたが、他のブラウザでも通用する手法のようです。

トレードオフ

やればやるほど「あのコードはちょっと…」「なんかスッと入ってこない」といわれる要因になる危険性があることも自覚しつつ、これらの結果を踏まえ uupaa.js をちょっと書き直します。