更なるパフォーマンス向上のヒント
Michael Wallent
Microsoft Corporation
October 4, 1999
日本語版最終更新日 2000年1月27日
この記事は、もともと MSDN Online Voices のコラム "DHTML Dude" に掲載されたものです。
Microsoft での開発サイクルの最も重要な部分の 1 つは、我々が製品のパフォーマンス チューニングに費やす時間です。パフォーマンス チューニングは、開発者が機能チェックの不可欠な部分と考えているものです。Win32® プログラムのチューニング方法に関する知識は年月とともに増え続けて、今では非常に大量の知識が蓄えられています。
多くの DHTML および HTML 開発者が直面する問題の 1 つは、ページを速くしたり遅くしたりすることについては、それほどの知識や情報がないことです。確かに、2MB のグラフィックスをページで使用してはならないということなど、いくつか簡単な知識はあります。しかし、我々が DHTML ページを速くするために行った作業の中で、みなさんのページのパフォーマンス向上に利用できる非常に興味深いヒントをいくつか発見しました。
私がこれからチューニングを行う例は、テーブル作成プログラムです。このプログラムは、document.createElement() と element.insertBefore() メソッドを使用して、1,000 行のテーブルを作成します。それぞれの行が 1 個のセルを含んでいます。それぞれのセルの内容は "Text" です。このコードには、どの程度の問題があるでしょうか。このわずかな量のコードに、どのくらいのチューニングを施すことができるでしょうか。実に大きなテーブルです。
私はまず、速いと思われるコードのかたまりから始めました。私は、変数を宣言しないとか、同じページで Visual Basic® Scripting Edition (VBScript) と Jscript® の両方を使用しないとか、本当に愚かだとわかっていることは何もしなかったつもりです。以下に出発点のコードを示します。
<html>
<body>
<script>
var tbl, tbody, tr, td, text, i, max;
max = 1000;
tbl = document.createElement("TABLE");
tbl.border = "1";
tbody = document.createElement("TBODY");
tbl.insertBefore(tbody, null);
document.body.insertBefore(tbl, null);
for (i=0; i<max; i++) {
tr = document.createElement("TR");
td = document.createElement("TD");
text = document.createTextNode("Text");
td.insertBefore(text, null);
tr.insertBefore(td, null);
tbody.insertBefore(tr, null);
}
</script>
</body>
</html>
最初のサンプルを表示してください。
テストはすべて、Windows NT® 4.0 が稼働する Intel Pentium II 233 (RAM 64MB) で Internet Explorer 5 を使用して (他に何があるでしょうか?) 行いました。ページはすべて、ローカル ドライブからロードされます。時間計測は、ページのロード開始からページが "静止" するまで (キューに入っていたイベントがすべて実行されて、画面が再描画されるまで) を計測しました。この基準となるページ ("テスト 1" と呼ぶことにします) は 2328 ミリ秒かかりました。
DHTML ページでしばしば驚くほど時間のかかる操作の 1 つは、グローバル オブジェクトの参照です。"ドキュメント"、"本体"、"ウインドウ" など、これらのオブジェクトの参照は、ローカルで宣言された変数の参照よりもはるかに時間がかかります。
最初に私がこのページに加える変更は、"theBody" という名前の変数で document.body の参照を "キャッシュ" することです。このプロパティの他の参照は 1 つだけなので、この変更はかなりマイナーな変更のはずです。
私は次のようなコード行を追加しました。
var theBody = document.body;
次に、
document.body.insertBefore(tbl, null);
という行を次のように変更しました。
theBody.insertBefore(tbl, null);
2 番目のサンプルを表示してください。
この変更は、全体的な時間には影響を与えません。ロード時間を 3 ミリ秒短縮したにすぎません。これは計測誤差の範囲内です。しかし、プライマリ ループで document.body オブジェクトを参照していた場合には、この変更は重大な影響を持っていたでしょう。
次に私が行った変更は、ドキュメント オブジェクトそのものの参照をキャッシュすることでした。ドキュメント オブジェクトは、テスト中、3002 回参照されます。ローカル変数でドキュメントをキャッシュして、その他のすべての参照ではそのローカル変数を参照するバージョンのソースを以下に示します。
<html>
<body>
<script>
var tbl, tbody, tr, td, text, i, max;
max = 1000;
var theDoc = document;
var theBody = theDoc.body;
tbl = theDoc.createElement("TABLE");
tbl.border = "1";
tbody = theDoc.createElement("TBODY");
tbl.insertBefore(tbody, null);
theBody.insertBefore(tbl, null);
for (i=0; i<max; i++) {
tr = theDoc.createElement("TR");
td = theDoc.createElement("TD");
text = theDoc.createTextNode("Text");
td.insertBefore(text, null);
tr.insertBefore(td, null);
tbody.insertBefore(tr, null);
}
</script>
</body>
</html>
3 番目のサンプルを表示してください。
このバージョンのページの実行には、2100 ミリ秒しかかかりませんでした。これはアプリケーションの合計時間の約 10 パーセントの節約になり、重大です。ドキュメント オブジェクトの取り出しには、ローカル変数をフェッチするために約 0.4 ミリ秒かかるだけです。
ドキュメントのロード速度の最適化として我々がよく行うのは、<SCRIPT> タグの "defer" (遅延) プロパティを設定することです。このプロパティを設定することが適切なのは、<SCRIPT> タグで実行するイミディエイト スクリプトがないときだけです (イミディエイト スクリプトは、関数内部にないコードです。このコードは、スクリプト ブロックがロードされるとすぐに評価されます)。defer プロパティが設定されているときには、Internet Explorer はスクリプトのロードと評価を待つ必要がありません。これは、ページのロードが速くなることを意味します。通常、これは、イミディエイト スクリプトを関数にリロケートしなければならず、その関数をドキュメントまたは本体オブジェクトの onload ハンドラとして設定しなければならないことを意味します。ボタンのクリックやマウスを特定の領域に移動することなど、ページがロードされた後のユーザー アクションに依存するスクリプトしかない場合は、これは非常に便利です。しかし、ページのロードと同時または直後に実行する必要があるスクリプトがある場合には、通常、それほどのメリットはありません。
defer プロパティを使用するように変更したバージョンを以下に示します。
<html>
<body onload="init()">
<script defer>
function init() {
var tbl, tbody, tr, td, text, i, max;
max = 1000;
var theDoc = document;
var theBody = theDoc.body;
tbl = theDoc.createElement("TABLE");
tbl.border = "1";
tbody = theDoc.createElement("TBODY");
tbl.insertBefore(tbody, null);
theBody.insertBefore(tbl, null);
for (i=0; i<max; i++) {
tr = theDoc.createElement("TR");
td = theDoc.createElement("TD");
text = theDoc.createTextNode("Text");
td.insertBefore(text, null);
tr.insertBefore(td, null);
tbody.insertBefore(tr, null);
}
}
</script>
</body>
</html>
4 番目のサンプルを表示してください。
このページのロード時間は 2043 ミリ秒でした。これは、最初のバージョンに比べて 12 パーセント、前のテストに比べて 2.5 パーセントの向上です。
次の拡張ははるかに重要ですが、必要な作業も少し増えます。要素を作成して、それらをツリーに挿入するとき、要素を大きいサブツリーに挿入して、サブツリーをプライマリ ドキュメントに挿入するのではなく、要素をプライマリ ドキュメントに挿入した方がはるかに効率的です。たとえば、1 個のセルを持ち、その中にいくらかのテキストを持つ新しいテーブル行を作成したい場合、次のような方法があります。
- <TR> を作成する
- <TD> を作成する
- TextNode を作成する
- TextNode を <TD> に挿入する
- <TD> を <TR> に挿入する
- <TR> を TBODY に挿入する
しかし、この方法より次の方法の方が速いです。
- <TR> を作成する
- <TD> を作成する
- TextNode を作成する
- <TR> を TBODY に挿入する
- <TD> を <TR> に挿入する
- TextNode を <TD> に挿入する
これまでのテストはすべて、最初の方法を使用していました。テスト 5 では 2 番目の方法を使用します。以下に実際のコードを示します。
<html>
<body onload="init()">
<script defer>
function init() {
var tbl, tbody, tr, td, text, i, max;
max = 1000;
var theDoc = document;
var theBody = theDoc.body;
tbl = theDoc.createElement("TABLE");
tbl.border = "1";
tbody = theDoc.createElement("TBODY");
tbl.insertBefore(tbody, null);
theBody.insertBefore(tbl, null);
for (i=0; i<max; i++) {
tr = theDoc.createElement("TR");
td = theDoc.createElement("TD");
text = theDoc.createTextNode("Text");
tbody.insertBefore(tr, null);
tr.insertBefore(td, null);
td.insertBefore(text, null);
}
}
</script>
</body>
</html>
5 番目のサンプルを表示してください。
テスト 5 は 1649 ミリ秒しかかかりませんでした。前のテストより約 25 パーセント速くなり、テスト全体のパフォーマンスは最初のバージョンより 30 パーセントも速くなりました。
次の変更は、固定テーブル (fixed-table) レイアウトを使用するようにテーブルを変更することです。固定テーブル レイアウトのテーブルの列幅は、テーブル内の <COL> タグによって、または <COL> タグがない場合は、行を各セルのスペースで均等に割ることによって設定されます。固定テーブル レイアウト スタイルはテーブルのパフォーマンスを改善します。それぞれのセルの内容のサイズを計算する必要がないからです。これは特に、多くの列を持つ大きなテーブルのパフォーマンスに恩恵を与えます。
これを追加するのは、カスケーディング スタイル シート (CSS) スタイルを追加するのと同じくらい簡単でした。
tbl.style.tableLayout = "fixed";
6 番目のサンプルを表示してください。
しかし、このテーブルには 1 つしかセルがないので、パフォーマンスの向上は 1.6 パーセントにすぎませんでした。もし、テーブルのセル数が多い場合は、より大きな向上が得られたでしょう。
最後の 2 つのテストでは、テキストをテーブル セルに挿入する方法を変えてみました。他のテストはすべて、TextNode を作成してから TD に挿入しました。テスト 7 では、テキスト ノードを挿入する代わりに、innerText でテキストを指定しました。以下にコードの変更を示します。
td.innerText = "Text";
7 番目のサンプルを表示してください。
驚くべきことに、これは大きな違いをもたらしました。前回のテストより 9 パーセントも速くなり、パフォーマンス改善の合計は 36 パーセントになりました。時間は、最初のバージョンの 2323 ミリ秒から最後のテストの 1473 ミリ秒に短縮されました。
すでに多くの人が、element.innerHTML を使用すると遅くて仕方がないということを知っています。どれぐらい遅いかを調べるために、私は innerText の代わりに innerHTML を使用して "Text" をセルに挿入するテスト ページを作成しました。パフォーマンスは文字どおり、がた落ちでした。合計時間は 3375 ミリ秒になり、前回のテストより 80 パーセントも遅くなりました。このたった 1 つの変更で、最初のバージョンより -5 パーセントに逆戻りしてしまいました。明らかに、innerHTML はかなり低速です。
すべての結果の試行回数と平均を表示することができます。
ここで、余談を少々。私は、みなさんが普通のスプレッドシートを作成するときとまったく同じように、Excel 2000 でそのページを作成しました。作成した後、[Web ページとして保存] 機能を使用して、HTML に変換しました。上のリンクでご覧になったクールなページができました。その後、私は Difference from Previous (前回との差) 行を追加していなかったことに気が付き、保存した HTML ページを再び Excel 2000 で開きました。多少のラウンドトリップ問題があるだろうと予想していましたが、まったくそんなことはありませんでした。それは完璧でした。100 パーセント、元のままでした。みなさんもご自分の Excel 2000 でそのページを開いて、ご自分の目で確認することができます。私はそれが気に入りましたし、みなさんもページを見るための特別なビューアを読み込む必要はありません。
HTML ページのチューニングは、Win32 アプリケーションのチューニングとまったく同じです。つまり、何が遅くて、何が速いかを知る必要があります。これらのヒントがみなさんのページを速くする上で役立つでしょう。
Michael Wallent は、Internet Explorer 担当のグループ プログラム マネージャです。
|