サイトトップ

Director Flash 書籍 業務内容 プロフィール

Optimizing Performance of ActionScript 3.0

Chapter 03 まとめたデータの扱い

□03-01 配列エレメントのすべてを素速く処理するには

データをまとめて納めるオブジェクトとして、まず頭に浮かぶのが配列です。そのエレメントすべてを素速く取出して扱うにはどうしたらよいかについてご説明します。forステートメントを使うのがお決まりですので、繰返し処理の最適化を考えることにもなります。この節では、つぎのようなことがらを採上げます。

    【配列エレメントのすべてを素速く処理するには】
  1. 繰返し処理のカウンタ変数は整数型で指定する
  2. Array.lengthプロパティの値は予め変数にとる
  3. 繰返し処理の中で複数回アクセスするオブジェクトは変数に入れる
  4. タイムライン変数やプロパティよりローカル変数の方が速い

多くの項目は、他の章でも扱われていることに気づかれたかもしれません。裏返せば、「配列エレメントのすべてを素速く処理する」というお題は、さまざまな最適化のコツを総合して取組む必要があるということです。それらをどのように組合わせるのか、応用問題として考えてみる価値があるでしょう。理解できている項目については、流し読みしても差支えありません。

03-01-01 繰返し処理のカウンタ変数は整数型で指定する
繰返し処理に用いられるforステートメントのシンタックスは、以下のように3つの指定をします。第1の初期化処理は、繰返し処理で用いるカウンタ変数の宣言と初期値の代入がお約束です。また、第3の更新処理はカウンタ変数値を、毎回書替えます。ほとんどの場合、整数の加減算(カウントアップまたはダウン)でしょう。

for (初期化処理; 継続条件; 更新処理) {
  // 繰返し処理の内容
}

とくに、カウンタ変数を配列エレメントのインデックスとして用いるなら、値は0以上の整数に決まります。01-03「数値は小数か整数かマイナスを含むのかを区別する」に述べたとおり、「カウンタの値が整数なら、変数を整数型で指定します」。さらに、「数値が負にならない場合は、uint型を指定するとより最適化がはかられます」。

スクリプト03-01-001【△】必ず整数となるカウンタ変数にはNumber型は好ましくない
  1. for (var i:Number = 0; i < 10; i++) {
  2.   // 処理内容
  3. }

スクリプト03-01-002【○】整数でしかも0以上のカウンタ変数はuint型で指定する
  1. for (var i:uint = 0; i < 10; i++) {
  2.   // 処理内容
  3. }

03-01-02 Array.lengthプロパティの値は予め変数にとる
forステートメントの第2の指定である継続条件には、配列エレメント数を示すArray.lengthプロパティがよく使われます。配列(my_array)のエレメントすべてを取出すとき、つぎのようなスクリプトをよく見かけます。けれど、まだ無駄があります。

スクリプト03-01-003【△】継続条件にプロパティを使うと繰返し参照される
  1. for (var i:uint = 0; i < my_array.length; i++) {   // 継続条件にプロパティを使う
  2.   // 処理内容
  3. }

継続条件は、繰返し処理を行うたびに確かめられます。そこにArray.lengthプロパティが使われていれば、ループ回数分プロパティにアクセスすることを意味します(スクリプト03-01-003第1行目)。だとすると、01-04「何度もアクセスするプロパティは変数に入れる」に述べたとおり、プロパティ値は予め変数(uint型)にとっておくべきです。

スクリプト03-01-004【○】プロパティ値は予め変数にとって継続条件に使う
  1. var nLength:uint = my_array.length;   // プロパティ値を変数にとる
  2. for (var i:uint = 0; i < nLength; i++) {   // 継続条件には変数を使う
  3.   // 処理内容
  4. }

03-01-03 繰返し処理の中で複数回アクセスするオブジェクトは変数に入れる
01-04「何度もアクセスするプロパティやオブジェクトは変数に入れる」に述べたとおり、「配列から取出したオブジェクトに何度もアクセスするとき」(01-04-02)は、オブジェクトを型指定した変数に入れます。前掲01-04-02のお題に少し手を加えて、最適化の要点をおさらいし、もう少し踏み込んだご説明をしましょう。タイムラインに動的に配置した複数のMovieClipインスタンスに、水平スクロールのアニメーションをさせます(図03-01-001)。

図03-01-001■タイムラインに置いた複数のMovieClipインスタンスを水平にスクロールさせる

インスタンスがそれぞれ水平にスクロールし、ステージの端を超えると反対側の端から表れる。

まず、MovieClipインスタンスは、[ライブラリ]のシンボルから動的につくります。そのためには、[ライブラリ]パネルのシンボルを選んだうえで、パネルメニューから[プロパティ]を選択します。[シンボルプロパティ]ダイアログボックスが開きますので、[リンケージ]の[ActionScript用に書き出し]をチェックし、[クラス]のフィールドにクラス名(MyClass)を入力します(図03-01-002)。

図03-01-002■[シンボルプロパティ]ダイアログボックスで[クラス]を設定する

[ライブラリ]パネルのメニューから[プロパティ]で[シンボルプロパティ]ダイアログボックスを開き、[リンケージ]の[ActionScript用に書き出し]をチェックして、[クラス]にクラス名MyClassを入力する。


Tips 03-01-001■クラス定義の自動生成
この例の場合、クラスを定義する必要はありません。定義されていないクラス名を[シンボルプロパティ]ダイアログボックスのクラスに設定すると、下図03-01-003のような警告のダイアログボックスが表れます。[OK]ボタンをクリックすれば、MovieClipクラスを継承した空の(追加するプロパティやメソッドのない)クラスが自動的につくられます

図03-01-003■定義されていないクラスは自動生成される

[シンボルプロパティ]ダイアログボックスに設定した[クラス]が定義されていないと、MovieClipクラスを継承した空のクラスが自動的につくられる。

スクリプトは、ふたつの関数で組立てます。第1に、関数xInitialize()は、[ライブラリ]からMovieClipインスタンスを指定の数つくり出し、タイムラインに配置するとともに、配列に納めます。第2は、前掲01-04-02のお題と同じく、DisplayObject.exitFrameイベント(定数Event.EXIT_FRAME)のリスナー関数xScroll()で、すべてのインスタンスを水平にスクロールさせます。

以下のフレームアクション(スクリプト03-01-005)は、最適化をほとんど考えていません。第1に、ふたつの関数ともに、forステートメントのカウンタ変数がNumber型で指定されています(第8行目および第17行目)。第2に、forループの中で、いちいち配列にインデックスを指定して、取出したエレメントのインスタンスに操作が加えられています(第9〜12行目および第18〜20行目)。第3に、リスナー関数(xScroll())のforステートメントは、継続条件にArray.lengthプロパティが用いられています(第17行目)。

スクリプト03-01-005【×】数値のデータ型や何度も参照するプロパティとオブジェクトへの配慮がない
    // フレームアクション
  1. var nCenterY:Number = stage.stageHeight / 2;
  2. var nWidth:int = stage.stageWidth;
  3. var mcs_array:Array = new Array();
  4. var nCount:uint = 5;
  5. var nSpeed:uint = 5;
  6. xInitialize();
  7. function xInitialize():void {
  8.   for (var i:Number = 0; i < nCount; i++) {
        // 配列エレメントを操作する
  9.     mcs_array[i] = new MyClass();
  10.     mcs_array[i].x = nWidth * i / nCount;
  11.     mcs_array[i].y = nCenterY;
  12.     addChild(mcs_array[i]);
  13.   }
  14.   addEventListener(Event.ENTER_FRAME, xScroll);
  15. }
  16. function xScroll(eventObject:Event):void {
  17.   for (var i:Number = 0; i < mcs_array.length; i++) {   // 継続条件にプロパティを使う
        // 配列エレメントを操作する
  18.     mcs_array[i].x += nSpeed;
  19.     if (nWidth < mcs_array[i].x) {
  20.       mcs_array[i].x -= nWidth;
  21.     }
  22.   }
  23. }

上記スクリプトの問題を改めたのが、以下のフレームアクション(スクリプト03-01-006)です。第1に、forステートメントのカウンタ変数は、配列インデックスとして使われますので、uint型で指定しました(第9行目および第20行目)。第2に、操作するインスタンスはローカル変数に入れて、配列エレメントへのアクセスを最小限に抑えています(第10行目および第21行目)。第3に、Array.lengthプロパティの値は予めローカル変数に入れて、forステートメントの継続条件にはその変数を用いました(第19〜20行目)。

スクリプト03-01-006【○】整数は整数型で指定して繰返し参照するプロパティやエレメントは変数にとる
    // フレームアクション
  1. var nCenterY:Number = stage.stageHeight / 2;
  2. var nWidth:int = stage.stageWidth;
  3. var mcs_array:Array = [];
  4. var nCount:uint = 5;
  5. var nSpeed:uint = 5;
  6. xInitialize();
  7. function xInitialize():void {
  8.   var nSpace:Number = nWidth / nCount;
  9.   for (var i:uint = 0; i < nCount; i++) {
  10.     var my_mc:MovieClip = new MyClass();   // オブジェクトをローカル変数にとる
        // ローカル変数のオブジェクトを操作する
  11.     my_mc.x = nSpace * i;
  12.     my_mc.y = nCenterY;
  13.     mcs_array[i] = my_mc;
  14.     addChild(my_mc);
  15.   }
  16.   addEventListener(Event.ENTER_FRAME, xScroll);
  17. }
  18. function xScroll(eventObject:Event):void {
  19. var nLength:uint = mcs_array.length;   // Array.lengthプロパティの値をローカル変数にとる
  20.   for (var i:uint = 0; i < nLength; i++) {   // 継続条件にローカル変数を使う
  21.     var my_mc:MovieClip = mcs_array[i];   // 配列エレメントをローカル変数にとる
        // ローカル変数のオブジェクトを操作する
  22.     var nX:Number = my_mc.x + nSpeed;   // プロパティ値をローカル変数にとる
  23.     if (nWidth < nX) {
  24.       nX -= nWidth;
  25.     }
  26.     my_mc.x = nX;   // ローカル変数の値をプロパティに設定する
  27.   }
  28. }

さらに第4として、リスナー関数(xScroll())のforステートメントで、ローカル変数にとったインスタンスのプロパティ値を、改めてローカル変数(nX)に納めています(スクリプト03-01-006第22行目)。前掲スクリプト01-04-004「プロパティ値や配列エレメントのインスタンスを予め変数にとる」では、最適化した例としてつぎのように書いていました。

  1. my_mc.x += 5;
  2. if (nWidth < my_mc.x) {
  3.   my_mc.x -= nWidth;
  4. }

しかし、インスタンスがすでに変数に納められていても、そのプロパティに複数回アクセスするときは、値をさらにローカル変数にとることで負荷がもうひと絞り減らせます。

Tips 03-01-002■Arrayインスタンスは配列アクセス演算子[]でつくる
後述03-05「Arrayインスタンスはコンストラクタでなくリテラルでつくる」のとおり、Arrayインスタンスをつくるのはコンストラクタを呼出すより配列アクセス演算子[]による方が処理は速いです。前掲フレームアクションの「悪い例」と「よい例」では、この配列(mcs_array)のつくり方も違っています。

○Arrayインスタンスはコンストラクタメソッドより速くつくる方法がある

var mcs_array:Array = new Array();


◎Arrayインスタンスは配列アクセス演算子[]の方がコンストラクタより速くつくれる

var mcs_array:Array = [];


[*筆者用参考]

Comparing local variable with property - wonderfl build flash online


03-01-04 タイムライン変数やプロパティよりローカル変数の方が速い
フレームアクションでタイムラインにvar宣言したタイムライン変数や、クラス定義に宣言したプロパティより、関数・メソッド内でvar宣言したローカル変数の方がアクセスは速いです。したがって、少しでも速さを稼ごうとするなら、タイムライン変数やプロパティをローカル変数に入れ直すという手があります。

たとえば、以下のフレームアクション(スクリプト03-01-007)は、0から999までの整数を配列(my_array)のエレメントに加えます。前掲スクリプト03-01-006の第9行目と同じforステートメントの書き方です(第5行目)。つまり、これでも合格点といえます。けれど、まだ最適化はできるということです。

スクリプト03-01-007【○】タイムライン変数より速くアクセスできる変数がある
    // フレームアクション
  1. var my_array:Array = [];   // タイムライン変数
  2. var nCount:uint = 1000;   // タイムライン変数
  3. iterateArray();
  4. function iterateArray():void {   // 関数定義
  5.   for (var i:uint = 0; i < nCount; i++) {   // 継続条件にタイムライン変数を用いる
  6.     my_array[i] = i;   // タイムライン変数の配列にエレメントを加える
  7.   }
  8. }

つぎのフレームアクション(スクリプト03-01-008)は、ふたつのタイムライン変数(my_arrayとnCount)を関数の中で改めてローカル変数(_arrayとcount)に入れ直しました(第5〜6行目)。ステートメント数は増えるものの、これで処理はさらに速められます。なお、配列の代入は参照が渡されますので、処理が終わった後とくにタイムライン変数の配列(my_array)を操作する必要はありません。

スクリプト03-01-008【◎】繰返しアクセスするタイムライン変数の値はローカル変数に入れるとさらに速められる
    // フレームアクション
  1. var my_array:Array = [];
  2. var nCount:uint = 1000;
  3. iterateArray();
  4. function iterateArray():void {   // 関数定義
  5.   var _array:Array = my_array;   // ローカル変数
  6.   var count:uint = nCount;   // ローカル変数
  7.   for (var i:uint = 0; i < count; i++) {   // 継続条件にローカル変数を用いる
  8.     _array[i] = i;   // ローカル変数の配列にエレメントを加える
  9.   }
  10. }

Tips 03-01-003■Array.lengthプロパティの値はローカル変数に入れる
前述03-01-02「Array.lengthプロパティの値は予め変数にとる」というのは、forループの処理が関数内で行われる場合には、プロパティ値をローカル変数に入れることになります。すると、処理はさらに速まります。

クラス定義におけるインスタンスプロパティについても同じです。前掲の最適化したフレームアクションをクラス(Test)として定義すると、以下のスクリプト03-01-009のようになります。ふたつのインスタンスプロパティ変数(my_arrayとnCount)を、メソッドの中で改めてローカル変数(_arrayとcount)に入れ直しています(第10〜11行目)。

スクリプト03-01-009【◎】クラスのインスタンスプロパティもローカル変数に入れるとアクセスが速められる
    // クラス定義ファイル: Test.as
  1. package {
  2.   import flash.display.Sprite;
  3.   public class Test extends Sprite {
  4.     private var my_array:Array = [];   // インスタンスプロパティ
  5.     private var nCount:uint = 1000;   // インスタンスプロパティ
  6.     public function Test() {
  7.       iterateArray();
  8.     }
  9.     private function iterateArray():void {
  10.       var _array:Array = my_array;   // ローカル変数
  11.       var count:uint = nCount;   // ローカル変数
  12.       for (var i:uint = 0; i < count; i++) {
  13.         _array[i] = i;
  14.       }
  15.     }
  16.   }
  17. }

[*筆者用参考]

「Flash Platform のパフォーマンスの最適化」「その他の最適化
BeInteractive!「ループの最適化

Assigning values to several types of variables - wonderfl build flash online

Iterating elements in member and local variables - wonderfl build flash online


作成者: 野中文雄
更新日: 2011年6月8日 MovieClipシンボルへのクラス定義について若干加筆。
更新日: 2011年1月23日 スクリプトに連番と行番号を追加。
更新日: 2011年1月15日 例のタイトルを具体化
作成日: 2011年1月5日


Copyright © 2001-2011 Fumio Nonaka.  All rights reserved.