
Ted Patrickは、Flexのエバンジェリストで、自身でブログを公開しています。
このセッションでは、Flash Player 9のパフォーマンスを最大限に生かすための、ActionScript 3.0の最適化について解説します。
まず強調しておきたいのが、速さだけを目的にして、はじめからすべてにおいて最速のコードを書くことを狙うよりも、完成したコードのどこかに処理の遅い関数があることが判明したとき、それに対して最適化を行うほうが最終的に良い結果を生む、ということです。このセッションでお伝えするのは、そのための知識だと思ってください。
Flashアプリケーションが実行しているとき、1フレームサイクルごとに次のステージが実行されています:
これらの3ステージは、互いに依存しており、どれか一つでも処理が長引けば、次のサイクルに進むのが遅くなります。このため、ActionScriptの実行に時間がかかるようなら、1フレームですべてを行うのではなく、数フレームに分けて実行するほうがよい場合があります。もしActionScriptの実行に時間がかかりすぎると、最悪の場合はスクリプトタイムアウトが発生します。
また、ネットワーク処理は、ActionScriptの実行と平行して行うことができます。ソケットイベントやHTTPイベントなどがこれにあたります。このような処理は、まずリクエストを出して、数フレームのあいだその結果が到着したことを確認し続け、結果を受け取ってはじめて次の処理を行います。見かけ上のマルチスレッドといえます。
とはいえ、Flash Playerはシングルスレッドで動作し、そのなかでグラフィックのレンダリングとActionScriptの実行が交代で行われます。ActionScript実行ステージが完了しないと、次のレンダリングステージには行きません。
任意のタイミングでステージをレンダリングするためのupdateAfterEvent()関数がありますが、これは、実行中のActionScriptがすべて完了していない状態でも「いいから無視してレンダリングしろ」という命令です。これを多用しすぎると、ActionScript処理が進まないまま、再レンダリングだけを重ねる結果となり、Flash Playerに過大な負担をかけることがあります。
また、1つのフレームサイクルで実行するように記述されたActionScriptは、それがどれだけ膨大であっても、Flash Playerはすべてを実行するまで次のステージに行かないので、タイムアウトすることがあります。これを回避するひとつの方法は、Flexの「mx.core.UIComponent.callLater()」関数を使うことです。この関数の引数にメソッドを指定すると、その関数を保留し、グラフィックのレンダリングステージを経て次のActionScript実行サイクルに到達したときに実行するよう、あとまわしにすることができます。これを使えば、例えば複雑な形状のドローイングオブジェクトを一気に3,000個作るのではなく、1フレームあたり30ずつ分けて作成してステージに配置するようなことが可能になります。
AS3の場合、厳密な型付けはバイトコード自体を最適化します。バイトコードとは、ActionScriptをマシンネイティブなコードに変換する前の中間コードで、SWFファイルに格納されているのはこのバイトコードです。Flash Player 9では、このバイトコードがアプリケーション読み込み時にジャストインタイム(JIT)コンパイルされ、ネイティブなコードとして実行されます。
厳密な型付けを行うことにより、メンバアクセスが非常に高速(4x-5x)になります。メンバアクセスとは、オブジェクトのプロパティを参照したり、配列から要素を取り出す際に用いる、以下のような、非常に使うことが多い処理です:
instance.x
instance[3]
メンバアクセスには2種類、「強い参照」と「弱い参照」があります:
つまり、変数に強い型付けをすることにより、構造上より速く動作する、ということになります。また、動的クラスは動作が遅いとされていますが、メンバアクセスに関しては、強い参照を使えば、速いのです。
以下は型付けの例で、変数に型付けがなされていないため、すべてが弱い参照を返します:
function a(o) //弱い参照が返る
{
return o; //弱い参照
}
function b(o) //弱い参照が返る
{
return.o.x; //弱い参照
}
function c(o:Object) //弱い参照が返る
{
return o.x //弱い参照
}
次は、変数に型付けがなされている場合です:
class Base extends object {
var x:int; //強い参照
var y:int; //強い参照
var z; //弱い参照
}
function b(o:Base):int{ //弱い参照を返す
return o.z; //弱い参照
}
function b(o:Base):int{ //強い参照を返す
return o.x; //強い参照
}
次は、動的クラスの場合です:
dynamic class Base extends object{
var x:int //強い参照
var y:int; //強い参照
}
function a (o:Base){ //弱い参照が返る
return o.z; //弱い参照
}
function a (o:Base):int{ //強い参照が返る
return o.x; //強い参照
}
配列においては、要素が密集して隙間が開いていない部分(dense portion)に関しては速いパス(fast path)を使ってアクセスできます。Dense Portionへのアクセスは、C++と同様の高速アクセスが可能です。間が抜けている部分(sparse portion)へは遅いパス(slow path)でしかアクセスできません。
var a:Array=[1,2,3,4,5];
a[1000]=2010;
a[1001]=2011;
a[2]; //速い
a[1000]; //遅い
明示的でない型変換は、速度の低下につながるため避けます。また、算術オペレーションを利用するときには予期せぬ型変換が行われることがあるため、気をつけます。数値を扱う際、intおよびuintのほうがメモリ消費量は少なく、カウンタとして使った場合ループの速度も速いのですが、間違って型変換が起こるよりは、はじめからNumberを使うほうが効率的な場合があります。
強い型付けは、生産性にも貢献します。なぜなら、コンパイラがより詳細なエラーを表示するため、問題に速くたどりつくことができ、デバッグ作業がシンプルになるからです。また、Flex Builderのコードヒント機能は、強い型付けを行うことを前提としています。型情報があれば、ドットを打った瞬間にメンバがコードヒントにリストされます。よってより速くコードを書くことができます。
Tedは、自分でMeasure.asというクラスを作成し、ActionScriptの特定部分のパフォーマンス測定に利用しています。Measure.start()呼び出しでタイマーがリセットされ、作動し、Measure.offset()でその行が実行されたタイミングでの経過時間を出力ウィンドウにtraceします。クラスの中身はgetTimer()を使ったシンプルなものです。これは近日中に彼のブログに掲載される予定です。
ここで、実際のループ処理を実行し、強い型付けがされたメンバへの参照のほうが、弱いものより6倍近く高速なことがデモンストレーションされました。
最後に、明日のMAX最終日(10月26日)朝の、スニークプレビューセッションでFlex ProfilerというActionScript 3.0の実行を、オブジェクトインスタンスごとの消費メモリなど詳細にプロファイルできるツールのプレビューがあることを告知し、このセッションは終了しました。