IE10 と Windows 8 における JavaScript のパフォーマンスの進歩


2012 年 5 月 31 日木曜日に、Windows 8 Release PreviewIE10 Platform Preview 6 が提供を開始されました。Windows 8 では、付属する 1 つの HTML5 ブラウジング エンジンによって、ブラウジング エクスペリエンス (Metro スタイルとデスクトップ) と、HTML5 および JavaScript を使う Metro スタイル アプリの両方に対応します。Release Preview では、IE9 で最初に導入された先進的な JavaScript エンジンである Chakra が大幅に改訂されています。各 Platform Preview のリリースごとに、私たちは Web での優れたパフォーマンスと、高いレベルの互換性、相互運用性、そしてセキュリティを兼ね備えたエンジンという目標に向けて前進を続けています。この記事では、先端的な Web アプリケーションのシナリオで優れたパフォーマンスを発揮できるよう JavaScript エンジンに施された強化点についてご紹介します。

本格的な Web アプリケーションを意識したパフォーマンス

Web アプリケーションは近年急激に進化しています。10 年前に Web の中心となっていたのは、静的なコンテンツの Web サイトでした。たとえばブログ小規模ビジネスのランディング ページ (英語)、Wikipedia などで見られるコンテンツがこれに当たります。AJAX の登場により増えてきたのが、FacebookJetsetter のような、より複雑でインタラクティブなサイトです。その後のパフォーマンスの進歩により、Office 365Bing 地図などのような、大規模で複雑なアプリケーションの作成が可能になりました。最近の動きとしては、W3C の標準 API の拡張、JavaScript パフォーマンスの向上、ハードウェア アクセラレーションを使ったグラフィック処理により、高度なゲームでさえ Web 上で動作するようになってきています。たとえば Angry Birds (英語)、Pirates Love Daisies (英語)、Cut The Rope (英語) などがこれに当たります。

さまざまな Web ページとそれぞれのパフォーマンス上の特徴を示した図。左側にある基本的な Web ページでは、ページ読み込み時間がパフォーマンスの指標となっている。右側には Web アプリケーション、HTML5 ゲーム、Windows 8 Metro スタイル アプリなどがあり、これらにおいては JavaScript 実行速度、DOM の対話的操作、グラフィック アクセラレーションなどがパフォーマンスに最も大きく影響する。

アプリケーションが進化するにつれ、ユーザー エクスペリエンスに影響を与えるパフォーマンス要素も変化します。従来的な Web サイトでは、初期のページ読み込み速度によって、ユーザーがコンテンツを目にするまでの所要時間が決まります。インタラクティブな Web サイトや大規模な Web アプリケーションの場合、DOM 操作の効率性、CSS 処理、メモリ内での大量の内部状態データの操作などがパフォーマンスを左右します。HTML5 ゲームでは、高速な Canvas のレンダリングと JavaScript の実行、効率的なガベージ コレクションなどが重要になります。ブラウザーのパフォーマンスは、さまざまなアプリケーションが持つ多様なニーズを考慮する必要のある、複雑な問題と言えます。

この記事では、ブラウザーのサブシステムの一つである JavaScript エンジンのみに焦点を絞ります。最近の JavaScript のパフォーマンスの向上により、多くの Web アプリケーションにとって、JavaScript の実行速度は制限要因ではなくなっています。一方で、パフォーマンスが向上するにつれ、JavaScript エンジンにさらに負荷をかける新しいシナリオも登場してきます。本格的な JavaScript 多用型アプリケーションのパフォーマンス要件を満たすため、私たちは常に Chakra の改良を図っています。

さまざまなサイトのスクリーンショットを 2 軸にプロットした 2 次元グラフ。Y 軸はその他のブラウザー コンポーネントの使用負荷、X 軸は JavaScript 実行速度。コンテンツ サイトは平面の左下 (他のブラウザー コンポーネントも JavaScript も最も使用負荷が小さい) に配置され、グラフィックを多用する Angry Birds などのゲームは右上に配置されている。
Web アプリケーションのパフォーマンス特性

Chakra の内部的なしくみ

IE9 での導入当初から、JavaScript エンジン Chakra は 2 つの要素を指針として設計されています。これらの要素の重要性は IE10 でも変わっていません。

  • ユーザー エクスペリエンスのクリティカル パスに含める処理を最低限に抑える: これには、どうしても必要になるまで処理を遅延させたり、処理そのものを回避したりすることのほか、アイドル時間の活用、処理の並行化によるアプリケーションの応答性確保などが含まれます。
  • 利用可能なハードウェアをすべて活用する: 利用可能な CPU コアをすべて活用し、可能な場合は特化された高度な CPU 命令 (たとえば Intel の SSE2) を生成します。

JavaScript エンジン Chakra が 2 つのプロセッサ コアを使用するようすを示した図。
Chakra の並行処理アーキテクチャ

Chakra はブラウザー サブシステムの一つでしかありませんが、それ自体が複数のコンポーネントから成り立っており、これらのコンポーネントの連動によって JavaScript コードの処理や実行を行っています。ブラウザーがダウンロードした JavaScript ファイルの内容は Chakra のパーサーに渡され、構文の検証が行われます。ファイル全体に適用される処理はこれだけで、以降のステップは個々の関数 (グローバル関数を含む) に対して行われます。関数を実行する際 (グローバル関数はパース直後に実行されます)、Chakra のパーサーはコードを抽象構文ツリー (AST: Abstract Syntax Tree) にまとめ、バイトコード ジェネレーターに渡します。バイトコード ジェネレーターは、インタープリターによる処理 (CPU による直接処理ではなく) に適した中間フォーム (バイトコード) を作成します。AST と関数のバイトコードはいずれも保存されるため、その後の実行時に再生成する必要はありません。次にインタープリターが呼び出され、関数を実行します。インタープリターは各オペレーションを実行する際、使用された入力のタイプについての情報 (プロファイル) を収集し、関数が呼び出された回数を記録します。

呼び出し回数が一定のしきい値に達すると、インタープリターは関数をコンパイル待ち行列に追加します。他のブラウザーと異なり、Chakra の JIT (Just-In-Time) コンパイラは専用の別スレッドで実行されるため、スクリプトの実行を妨げることはありません。コンパイラの唯一の仕事は、コンパイル待ち行列に追加された各関数を最適化されたマシン命令に変換することです。関数のコンパイルが完了すると、メインのスクリプト スレッドに対して、マシン コードが用意できた旨が通知されます。次に関数が呼び出されると、関数のエントリ ポイントは新たにコンパイルされたマシン コードへとリダイレクトされ、CPU 上で直接処理が行われます。1 回か 2 回呼び出されただけのコードはコンパイルされないという点が重要です。これによって時間やリソースが節約されます。

JavaScript はマネージド ランタイムの一種で、メモリ管理は開発者からは見えず、使用されなくなったオブジェクトを定期的にクリーン アップする自動ガベージ コレクターによって行われます。Chakra に採用されているのは保守的で準ジェネレーショナルな、マーク アンド スイープ型のガベージ コレクターで、作業の多くを専用のスレッドで並行して行うため、ユーザー エクスペリエンスの妨げとなるスクリプト実行の中断は最小限に抑えられます。

このアーキテクチャにより、Chakra はページ読み込み中にほぼ即時的に JavaScript コードの実行を開始することができます。一方で、JavaScript の処理が特に多くなると、Chakra は最大 3 つの CPU コアをフル活用して、スクリプトの実行、コンパイル、ガベージ コレクションを同時に行います。

高速なページ読み込み

比較的静的な Web サイトであっても、インタラクティブな動作の実現や、広告、ソーシャル共有などの用途で JavaScript が使われることは珍しくありません。このことはデータにも表れており、Steve Souders 氏の HTTP Archive (英語) で報告されているとおり、Alexa のランキングの上位 100 万ページ (英語) で使用されている JavaScript のボリュームは増加の一途を辿っています。

Alexa のランキングの上位 100 万ページで使用されている JavaScript のボリュームを示したグラフ
Alexa のランキングの上位 100 万ページで使用されている JavaScript のボリューム (英語)

ブラウザーの JavaScript エンジンはこういった Web サイトに含まれる JavaScript コードをすべて処理しなければならず、また、各スクリプトのグローバル関数の処理を終えるまでコンテンツのレンダリングは完了できません。このため、このクリティカル パスに含める処理の量は最小限に抑える必要があります。Chakra のパーサーとバイトコード インタープリターは、この目的を念頭に置いた設計となっています。

バイトコード インタープリター: ページ読み込み中に実行される JavaScript コードでは、1 回限りの初期化やセットアップを行うケースがよくあります。総合的なページ読み込み時間を短縮するには、このコードの実行を即座に (つまり Just-In-Time コンパイラがコードを処理してマシン命令を生成するのを待つことなく) 開始する必要があります。インタープリターは、バイトコードへの変換が完了ししだい、JavaScript コードの実行を開始します。最初の命令が実行されるまでの時間をさらに短縮するため、Chakra は遅延パースと呼ばれるメカニズムによって、実行直前のコードだけに絞ってバイトコードの処理と生成を行います。

遅延パース: 11 個の人気 Web サイトで実際に実行されるコードの割合を示したグラフ。各サイトの数値は 30% 強~ 50% 強。Microsoft Research の JSMeter (英語) プロジェクトにより、一般的な Web ページで実際に実行されるコードはダウンロードしたコードの 40% ~ 50% にとどまることが示されています (右のグラフ参照)。直感的に考えても、これは不自然なことではありません。jQuery (英語) や Dojo (英語) といった人気のある JavaScript ライブラリや、Office 365 で使用されているもののようなカスタマイズされた JavaScript ライブラリを組み込んでいても、実際に使用するのはライブラリがサポートする機能の一部にとどまることが一般的です。

こういったシナリオに効率よく対応するため、Chakra ではソース コードに対して、最も基本的な、構文のみのパース処理を行います。残りの作業 (抽象文法ツリーの作成やバイトコードの生成) は、個々の関数が呼び出される際に逐次的に行われます。この戦略を採用することにより、Web ページ読み込み中のブラウザーの応答性が向上するだけでなく、メモリ使用量も削減することができます。

IE9 では、Chakra の遅延パースには 1 つ制約がありました。他の関数内にネストされた関数を、ネスト先の関数と共にすぐパースしなければならないという点です。多くの JavaScript ライブラリで、コードのほとんどを 1 つの大きな関数内にネストする "モジュール パターン (英語)" という記述方法が採用されているため、この制約は結果として重要なポイントとなりました。IE10 の Chakra ではこの制約が取り除かれ、ただちに実行されないすべての関数について、パースとバイトコード生成を遅延させるようになっています。

JavaScript 多用型アプリケーションでのパフォーマンス向上

IE10 では、IE9 と同様に、本格的な Web アプリケーションのパフォーマンス向上に力を注いでいます。しかし、Web アプリケーションが JavaScript のパフォーマンスに依存する度合いはさまざまです。IE10 での強化点についてご紹介するため、ここでは、Chakra の改良点によって大幅なパフォーマンス向上が実現する、JavaScript 多用型のアプリケーションに着目しましょう。HTML5 を使ったゲームやシミュレーションは、JavaScript 多用型アプリケーションの代表的な例です。

IE10 開発の初期段階で、私たちは人気のある JavaScript のゲーム (Angry Birds (英語)、Cut the Rope (英語)、Tankworld (英語) など) やシミュレーション (FishIE Tank (英語)、HTML5 Fish Bowl (英語)、Ball Pool (英語)、Particle System (英語) など) をサンプルとして分析を行い、どのようにパフォーマンスを向上させればユーザー エクスペリエンスに最も大きな効果があるか理解しようと努めました。分析の結果、いくつかの特徴やコーディングのパターンが明らかになりました。これらのアプリケーションに共通しているのは、高頻度のタイマー コールバックを使って動作しているという点です。多くのアプリケーションではレンダリングに Canvas を使用していますが、DOM 要素アニメーションを使用するものや、両方を組み合わせているものもあります。ほとんどのアプリケーションでは、コード (アプリケーション コード、または Box2d.js (英語) などのライブラリ) の少なくとも一部はオブジェクト指向スタイルで記述されています。短い関数、プロパティの頻繁な読み書き、多態性などもよく見られる特徴です。いずれのアプリケーションも浮動小数点演算を行い、多くのアプリケーションでは相当量のメモリ割り当てを行うため、ガベージ コレクターに負荷がかかっています。こういった共通のパターンが、IE10 でのパフォーマンス向上に向けた取り組みの焦点となりました。次のセクションでは、これらに応えて私たちが行った変更についてご紹介します。

Just-In-Time コンパイラ – 見直しと改良

IE10 では、Chakra の JIT コンパイラに大幅な改良が加えられています。サポートされるプロセッサ アーキテクチャには x64 と ARM の 2 つが新たに加わり、JavaScript アプリケーションが使用されるのが 64 ビット PC や ARM ベースのタブレットであっても、CPU での直接処理の恩恵を受けることができるようになっています。

また、マシン コード生成における基本的なアプローチを変更しました。JavaScript は非常に動的な言語であるため、コードを生成する際にコンパイラが把握できる情報が限られます。たとえば下の関数をコンパイルする場合、コンパイラは関連するオブジェクトの形状 (プロパティ レイアウト) や、オブジェクトのプロパティのタイプを把握することができません。

function compute(v, w) {

return v.x + w.y;

}

IE9 の Chakra のコンパイラでは、すべてのプロパティを実行時に探し、可能性のあるすべてのオペレーション (上の例では整数値の加算、浮動小数点での加算、そして文字列の連結まで) をハンドルするコードを生成していました。これらのオペレーションには、マシン コードで直接ハンドルされるものもあれば、Chakra のランタイムによる補助が必要なものもありました。

IE10 では、JIT コンパイラはプロファイル ベースの、タイプごとに特化したマシン コードを生成します。つまり、特定の形状のオブジェクトや特定のタイプの値に特化したマシン コードです。適切なコードを生成するためには、コンパイラは使用される入力値のタイプを把握している必要があります。JavaScript は動的な言語であるため、ソース コードからはこの情報は得られません。そこで、Chakra のインタープリターを強化し、実行時にこの情報を収集することにしました。これを動的プロファイリングと呼びます。関数の JIT コンパイルが予約されると、コンパイラはインタープリターが収集した実行時プロファイルを調べ、予想される入力に適したコードを生成します。

インタープリターは監視対象の処理について情報を集めていますが、プログラムを実行した結果のランタイム値が、コードを生成する際に想定されていたものとは異なる場合もあります。コンパイラは、何らかの想定を行うたびに、それについてのランタイム チェックを生成します。後でコードを実行した結果、予想外の値が得られた場合、チェックは失敗し、特化したマシン コードからのベイルアウトが行われて、処理はインタープリターによって続行されます。ベイルアウト (チェックの失敗) の原因は記録され、インタープリターが追加のプロファイル情報を収集し、関数は想定内容を調整した上で再コンパイルされます。ベイルアウトと再コンパイルは、IE10 で導入されたまったく新しい機能です。

この結果、IE10 の Chakra のコンパイラでは、コードに対して生成されるマシン命令の量が削減されており、全体的なメモリ使用量が抑制されると共に、処理速度も向上しています。これは、前述の HTML5 によるゲームやシミュレーションのような、浮動小数点演算やオブジェクト プロパティへのアクセスを行うアプリで特に効果的です。

さらに、JavaScript コードをオブジェクト指向スタイルで書いている場合は、Chakra による関数のインライン化サポートの恩恵を受けることができます。オブジェクト指向コードでは、比較的小さなメソッドが多数含まれることが多く、関数の実行にかかる時間と比べて、関数呼び出しのオーバーヘッドの比重が大きくなります。Chakra による関数のインライン化では、このオーバーヘッドを低減することができますが、より重要な点として、従来からある他のコンパイラ最適化技術 (ループ不変量コード移動、コピー伝播など) の適用範囲を大きく広げることができます。

浮動小数点演算の高速化

ほとんどの JavaScript プログラムでは、ある程度の整数演算を行います。次の例が示すとおり、算術計算が主な目的でないプログラムであっても、ループの繰り返し変数や配列のインデックスなどの用途で整数値が使用されていることが一般的です。

function findString(s, a) {

for (var i = 0, al = a.length; i < al; i++) {

if (a[i] == s) return i;

}

return -1;

}

一方、浮動小数点演算は通常、ゲームやシミュレーション、音声、画像、ビデオなどの処理といった、特定の種類のアプリケーションでのみ使用されます。従来はこういったアプリケーションが JavaScript で書かれることはあまりありませんでしたが、昨今のブラウザーのパフォーマンス向上により、JavaScript による実装も現実的になっています。IE9 の Chakra は、より一般的な整数演算向けに最適化されていました。IE10 では、浮動小数点演算が劇的に改良されています。

function compute(a, b, c, d) {

return (a + b) * (c − d);

}

上の簡単な関数を例として考えてみましょう。JavaScript コンパイラでは、ソース コードから引数 a、b、c、d のタイプを判別することはできません。IE9 のコンパイラでは、引数が整数値であるものと仮定し、整数値向けの高速なマシン命令を生成します。処理を実行してみて、実際に引数が整数値だった場合、この方法でまったく問題ありません。しかし浮動小数点数が使われていた場合、速度面で大きく劣る、Chakra ランタイムのヘルパー関数に頼ってコードを処理しなければなりません。中間値をヒープ上でボックス化/アンボックスする必要があるため、関数呼び出しのオーバーヘッドはさらに悪化します (Chakra を含むほとんどの 32 ビット JavaScript エンジンでは、個々の浮動小数点値はヒープに割り当てる必要があります)。上の例では、各オペレーションの結果について、それぞれヒープ割り当てを行い、ヒープに値を格納し、次のオペレーションのためにヒープから値を回収する必要がありました。

IE10 のコンパイラでは、インタープリターが収集したプロファイル情報を活用して、速度が劇的に向上する浮動小数点向けのコードを生成します。上の例で、プロファイルから引数がすべて浮動小数点数である可能性が高いことが判明した場合、コンパイラは浮動小数点演算向けのマシン命令を生成します。コード全体がわずか 3 つのマシン命令で処理され (すべての引数があらかじめレジスタに格納されている場合)、中間値はすべてレジスタに格納されるため、ヒープ割り当ては最終的な結果を返す際の 1 回だけです。

これによって、浮動小数点を多用するアプリケーションのパフォーマンスは非常に大きく向上します。実験から、IE10 での浮動小数点演算は IE9 よりも約 50% 高速であることがわかっています。さらに、メモリ割り当てが削減されるため、ガベージ コレクションの頻度も下がります。

オブジェクトやプロパティへのアクセス速度向上

JavaScript オブジェクトは、論理的に関係のある値のセットをグループ化する際に便利なメカニズムで、幅広く使用されています。IE10 では、オブジェクトの割り当てやプロパティへのアクセスにおいて改良が施されているため、JavaScript オブジェクトを構造化されたオブジェクト指向プログラミング スタイルで使用している場合も、単に柔軟性に優れた値のパッケージとして使用している場合も、パフォーマンスが大きく向上します。

前述のとおり、JavaScript ではコンパイルの段階ではオブジェクトの形状がわからないため、プロパティへの効率的なアクセスが困難です。JavaScript オブジェクトは、事前定義されたタイプやクラスなしでアドホックに作成される場合があります。新しいプロパティがその場でオブジェクトに追加される (あるいはオブジェクトから削除される) こともあり、プロパティの順序も一定しません。このため、たとえば次の関数をコンパイルする場合、Vector オブジェクト上のプロパティ x、y、z の所在は、コンパイラにはわかりません。

Vector.prototype.magnitude = function() {

return Math.sqrt(this.x * this.x + this.y * this.y + this.z * this.z);

}

IE9 では、プロパティへのアクセスを大幅に高速化するインライン キャッシュが導入されました。インライン キャッシュは、オブジェクトの形状と、オブジェクトのメモリ内の各プロパティの位置を記憶します。インライン キャッシュではオブジェクトの形状を 1 種類しか記憶できないため、関数で扱うオブジェクトがすべて同じ形状の場合にのみ効果を発揮します。IE10 では、複数の異なる形状のオブジェクトを扱う (多態的な) コードでのパフォーマンスを向上させる、二次的なキャッシュ メカニズムが追加されています。

プロパティの値を読み取る際、コンパイラは、オブジェクトの形状がインライン キャッシュに格納されているものと一致することを確認する必要があります。このため、IE9 のコンパイラは、プロパティにアクセスするたびに、ランタイムの形状チェックを生成します。プログラムはしばしば、同じオブジェクトの複数のプロパティを (下の例のように) 頻繁に読み書きするため、その都度チェックによるオーバーヘッドが生じます。

function collide(b1, b2) {

var dx = b1.x - b2.x;

var dy = b1.y - b2.y;

var dvx = b1.vx - b2.vx;

var dvy = b1.vy - b2.vy;

var distanceSquare = (dx * dx + dy * dy) || 1.0;

//...

}

IE10 の Chakra では、予想されるオブジェクト タイプに応じて専用のコードが生成されます。注意深いシンボル トラッキングとベイルアウト/再コンパイル機能により、新しいコンパイラではランタイムの形状チェックが行われる回数が劇的に削減されています。上の例の場合、形状チェックを個別に 8 回行うのではなく、b1 と b2 の 2 つについてのみチェックを行います。さらに、オブジェクトの形状が特定されると、プロパティの所在もすべて判明し、C++ に劣らない効率的な読み書きが可能になります。

ECMAScript 5 では、アクセサー プロパティと呼ばれる新しい種類のプロパティがオブジェクトに含まれる場合があります。アクセサー プロパティは、カスタマイズされた get/set 関数によって読み書きの処理を行う点が、従来のデータ プロパティと異なります。アクセサー プロパティは、データのカプセル化、計算プロパティ、データの検証、変更通知に便利なメカニズムです。Chakra の内部的なタイプ システムとインライン キャッシュは、アクセサー プロパティに対応した設計になっており、アクセサー プロパティの値を効率よく読み書きすることができます。

HTML5 のゲームやアニメーションを書く場合、しばしば重力を反映したリアルな動きやオブジェクトどうしの衝突などを再現するため物理エンジンが必要になります。ごくシンプルな物理演算であれば自分でエンジンを書く方法も考えられますが、より複雑なものが必要な場合は、Box2d.js (英語) (Box2d よりポート) など、人気のある JavaScript 向け物理演算ライブラリを使用するのが一般的です。こういったライブラリでは、しばしば Point、Vector、Color などの小さなオブジェクトを使用します。アニメーションでは、フレームごとにこういったオブジェクトが多数作成され、すぐに破棄されます。このため、JavaScript ランタイムによるオブジェクトの作成は効率的に行うことが重要です。

var Vector = function(x, y, z) {

this.x = x;

this.y = y;

this.z = z;

}

 

Vector.prototype = {

//...

normalize : function() {

var m = Math.sqrt((this.x * this.x) + (this.y * this.y) + (this.z * this.z));

return new Vector(this.x / m, this.y / m, this.z / m);

},

 

add : function(v, w) {

return new Vector(w.x + v.x, w.y + v.y, w.z + v.z);

},

 

cross : function(v, w) {

return new Vector(-v.z * w.y + v.y * w.z, v.z * w.x - v.x * w.z, -v.y * w.x + v.x * w.y);

},

//...

}

IE10 では、オブジェクト作成をスムーズにするため、JavaScript オブジェクトの内部レイアウトが最適化されています。IE9 では、オブジェクトにはすべて、固定サイズのヘッダーと拡張可能なプロパティ配列が含まれていました。後者は、オブジェクトの作成後に追加される可能性のある追加プロパティに対応するために必要です。すべての JavaScript アプリケーションがこの柔軟性を活用するわけではなく、オブジェクトを作成した段階でほとんどのプロパティが揃っていることも多々あります。このことを利用し、Chakra はこういったオブジェクトのプロパティの多くを、ヘッダーと共に直接割り当てます。結果、オブジェクトを作成するたびに発生するメモリ割り当てが 2 回ではなく 1 回になります。この変更により、オブジェクトのプロパティを読み書きする際に必要なメモリの逆参照の回数も削減され、レジスタの使用率が改善されます。オブジェクト レイアウトの改良と、ランタイムの形状チェックの頻度低減により、プロパティへのアクセスは最大 50% 高速になっています。

ガベージ コレクションの強化

前述のとおり、HTML5 のゲームやアニメーションでは、オブジェクトの作成と破棄が高い頻度で行われることが一般的です。JavaScript プログラムでは、破棄されたオブジェクトを明示的に破壊してメモリを回収することはありません。使用されていないオブジェクトから定期的にメモリを回収し、新しいオブジェクトが使用するスペースを空ける作業は、エンジンのガベージ コレクターに任されています。ガベージ コレクションが自動的に行われることにより、プログラミングは容易になりますが、コレクターの作業のために、JavaScript の実行が定期的に中断されてしまうことが一般的です。コレクターの実行に時間がかかれば、ブラウザー全体が応答しなくなる可能性もあります。HTML5 のゲームでは、わずかな (何十ミリ秒というレベルの) 中断でさえ、アニメーションの処理落ちとしてユーザーに認識されてしまうため、これは重要な問題です。

IE10 では、メモリ アロケーターとガベージ コレクターにいくつかの改良を施しました。オブジェクト レイアウトの変更と、浮動小数点演算向けに専用のマシン コードを生成することによって、メモリ割り当ての頻度が引き下げられている点については、既にお話ししたとおりです。これに加え、新しい Chakra では、リーフ オブジェクト (数や文字列など) の割り当てを、別個のメモリ領域から行います。リーフ オブジェクトは他のオブジェクトへのポインターを持たないため、ガベージ コレクションの際に通常のオブジェクトほど注意を払う必要はありません。リーフ オブジェクトの割り当てを別個のメモリ領域から行うことには、2 つのメリットがあります。1 つ目のメリットは、マーク段階でこの領域全体をスキップすることができるため、所要時間が短縮される点です。2 つ目は、並行してコレクションを行っている間に、リーフ オブジェクトの割り当てを新たに行う場合に、影響を受けるページを再スキャンする必要がない点です。Chakra のコレクターはメインのスクリプト スレッドと並行して実行されるため、処理済みのページ上でスクリプトがオブジェクトを変更したり、新しいオブジェクトを作成したりする可能性があります。使用中のオブジェクトが回収されてしまわないよう、Chakra はマーク処理を開始する前にページを書き込み保護します。マーク中に書き込みが行われたページは、後でメインのスクリプト スレッドで再スキャンする必要があります。リーフ オブジェクトではこういった処理は必要ないため、リーフ オブジェクト用のスペースに由来するページでは、書き込み保護や再スキャンも必要ありません。これによって、メインのスクリプト スレッドの貴重な時間を節約し、中断を減らすことができます。HTML5 のゲームやアニメーションでは、浮動小数点数を多用し、割り当てられたメモリの多くをヒープボックス化された数値に使用することが多いため、この変更によるメリットは特に大きくなります。

ユーザーが Web アプリケーションに直接関与する際は、アプリケーションのコードはなるべく高速に実行される必要があるため、ガベージ コレクションによる遅延は一切発生しないことが理想的です。しかし、ユーザーがブラウザーから他の画面に切り替えた場合、あるいは単に別のタブに切り替えた場合は、アクティブでなくなったサイトやアプリケーションのメモリ使用量を削減することが重要です。このため、IE9 の Chakra では、JavaScript コードを終了する際に十分なメモリが割り当てられていれば、ガベージ コレクションを開始していました。これはほとんどのアプリケーションで有効な方法でしたが、HTML5 のゲームやアニメーションのように高頻度のタイマーを使って動作しているアプリケーションでは問題がありました。こういったアプリケーションにとっては、この方法ではコレクションが開始される頻度が高すぎ、フレーム落ちやユーザー エクスペリエンスの全体的な低下につながっていました。この問題が最もはっきり表れていた例としては Tankworld (英語) が挙げられますが、HTML5 を使った他のシミュレーションでも、ガベージ コレクションの頻繁な発生によるアニメーションの中断が見られました。

IE10 では、ガベージ コレクションのタイミングをブラウザーの他の部分と連動させることで、この問題を解決しました。新しい Chakra では、スクリプト実行完了時のガベージ コレクションを遅延させ、一定時間スクリプトの動作がなかった場合に、ブラウザーにコールバックを要求します。スクリプトが実行されないまま規定の時間に達するとコレクションが開始され、時間内にスクリプトが実行された場合はコレクションはさらに延期されます。この方法により、ブラウザー (またはブラウザーのタブ) がアクティブでなくなった場合にメモリ使用量を削減することができ、しかもアニメーションを使ったアプリケーションでのコレクション発生頻度を大幅に引き下げることができます。

これらの変更により、メイン スレッドでガベージ コレクションに費やされる時間は、計測対象の各 HTML5 シミュレーションで平均して 4 分の 1 に短縮されました。ガベージ コレクションが JavaScript 実行時間に占める割合は、27% 程度から 6% 程度まで低減されています。

まとめ

IE10 では、JavaScript 多用型のアプリケーション、特に HTML5 を使ったゲームやシミュレーションで、劇的なパフォーマンス向上を実現します。これは、JIT コンパイラへの基幹機能の追加や、ガベージ コレクターの挙動の変更など、Chakra に施された重要な改良点によるものです。

IE10 の開発が終盤に向かうにつれ、多くを達成してきたことを実感すると共に、パフォーマンス向上は終わりなき挑戦であることを痛感しています。近代的なブラウザーと JavaScript エンジンの限界を試すような新しいアプリケーションが、毎日のように登場してきています。次のリリースでも多くの挑戦が待ち受けていることは間違いないでしょう。

また、チームでは JavaScript 開発者の皆さんからのご意見をお待ちしています。IE10 の新しい機能やパフォーマンス向上が、新しいユーザー エクスペリエンスの創造や、既存のアプリケーションの改良につながったケースがありましたら、ぜひお聞かせください。IE のパフォーマンスで制約を感じる点があった場合も、ぜひお知らせください。ブログにいただいたコメントにはすべて注意深く目を通しています。IE10 と Windows 8 が、最も包括的でパフォーマンスに優れたアプリケーション プラットフォームとなるよう、チーム一同引き続き努力していきたいと思います。

— JavaScript 担当プログラム マネージャー Andrew Miadowicz

Comments (0)

Skip to main content