サイトトップ

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

Now on Sale!!
ActionScript 3.0プロフェッショナルガイド』(毎日コミュニケーションズ)

『ActionScript 3.0プロフェッショナルガイド』をテキストにした短期集中講座
基礎から学ぶActionScript 3.0

■Mailing List: ActionScript 3.0


Adobe MAX Japan 2009

ActionScript 3.0におけるパフォーマンス向上のヒント

本稿は、ActionScript 3.0のスクリプティングで、パフォーマンスを高めるテクニックを解説する。ActionScript 3.0は、最適化されたAVM2(ActionScript Virtual Machine 2)で動作する[*1]。そのパフォーマンスを引出すポイントおよび、さまざまな小ネタをアラカルトで紹介する。内容の多くは、ActionScript 2.0でも活用できるだろう。

【追記】本講演の内容に基本的な説明や補足を加えて、Adobeデベロッパーセンターに「ActionScript 3.0におけるパフォーマンス向上のヒント」として寄稿した。

[*1] AVM2(ActionScript Virtual Machine 2)について詳しくは、gihyo.jp連載「ActionScript 3.0で始めるオブジェクト指向スクリプティング」の第1回「Flash Player 9とActionScript 3.0」の「Flash Player 9とは」をお読みいただきたい。


01 データ型を指定する
AdobeのMatt Chotin氏が米国AdobeサイトのDeveloper Centerに「Tips for tuning ActionScript 3.0 performance for Flex and Flash developers」という記事で、ActionScript 3.0の処理を最適化するためのポイントを挙げている(図001)[*2]

図001■型指定がバイトコードを最適化する!

バイトコードが最適化されるというのは、Flash Player上におけるActionScriptの処理効率が高まることを意味する。

変数や関数に対するデータ型の指定の仕方はつぎのとおりだ。

【変数の型指定】

var 変数:データ型;
変数 = 値;

【関数の型指定】

var 変数:データ型 = 値;

function 関数(引数:データ型):戻り値のデータ型 {
  // 処理内容
  return 戻り値;
}

ActionScript 3.0で型指定による最適化の恩恵を受けるには、「強い参照」(strong reference)が必要だ。それは、型指定とドットシンタックスにより得ることができる。

    【強い参照】
  1. データ型を指定する。
  2. ドットシンタックスを用いる。
    [シンタックス] オブジェクト.プロパティ
    my_mc.x
    【弱い参照】
  1. データ型を指定しない。
  2. 配列アクセスを用いる。
    [シンタックス] オブジェクト[プロパティ名]
    my_mc["x"]

たとえば、整数を格納するaというプロパティをObjectインスタンスに設定したとき、このプロパティには型指定ができない。

var myObject:Object = new Object();
myObject.a = 0;
// myObject.a = "test";   // どのようなデータでも代入できる

カスタムクラスを定義すれば、プロパティのデータ型が指定でき、処理も最適化されるので、プロパティへのアクセスは速くなる(スクリプト001)[*3]

スクリプト001■int型のブロパティをもったクラスの定義

// ActionScript 3.0クラス定義ファイル: MyObject.as
package {
  public class MyObject {
    public var a:int;
  }
}

クラスMyClassにint型で宣言したプロパティaに文字列(String型)の値を代入しようとすれば、Objectクラスの場合とは異なり、[コンパイルエラー]が生じる(図002)。

図002■クラス定義で型指定したプロパティに異なるデータ型の値を入れようとするとコンパイルエラー

[*2] プレゼンテーションのデータは、前出「Tips for tuning ActionScript 3.0 performance for Flex and Flash developers」からPDFファイルでダウンロードできる。

[*3] クラスの定義について詳しくは、前出注[*1]「ActionScript 3.0で始めるオブジェクト指向スクリプティング」の第18回「カスタムクラスを定義する」参照。


02 型指定した変数を活用する
ひとつの処理の中で何度もオブジェクトのプロパティにアクセスするときは、型指定した変数を活用できることが少なくない。

●配列エレメントを処理する
配列(my_array)のエレメントすべてをforループで取出して処理しようとするとき、つぎのよう記述することができる。

for (var i:int = 0; i<my_array.length; i++) {
  // 配列エレメントmy_array[i]に対する処理
}

継続条件に指定したArray.lengthプロパティの値が、ループするたびに毎回参照される。この値は、予め型指定した変数(nLength)に納めておく方がよい(スクリプト002)[*4]

スクリプト002■配列のループ処理ではArray.lengthプロパティの値は予め型指定した変数に納めておく

var nLength:uint = my_array.length;
for (var i:int = 0; i<nLength; i++) {
  // 配列エレメントmy_array[i]に対する処理
}

なお、forステートメントのカウンタ変数(i)は通常整数を用いるので、浮動小数値(Number型)でなく整数(intまたはuint型)で指定する方が処理は速くなる。

●TextFieldインスタンスに文字列を加える
ActionScript 3.0では、TextField.textプロパティに加算後代入演算子+=で文字列を追加しようとすると、デフォルトでは警告(Warning)が示され、演算子+=でなくTextField.appendText()メソッドを使うよう促される(図003)。

図003■TextField.textプロパティに演算子+=でテキストを追加すると警告が表示される

では、forループでTextFieldインスタンスに、文字列を連続して加えてみよう。たとえば、0から9までの数字をTextFieldインスタンスに続けて追加するなら、forステートメントをつぎのように記述すればよいだろうか。

for (var i:int = 0; i<10; i++) {
  test_txt.appendText(String(i));
}

TextFieldインスタンスに文字列を設定する操作は、そもそも負荷が高い。forステートメントで繰返すコードブロックからは、できるだけ重い処理を外すべきだ。

繰返す文字列の連結にはStringで型指定した変数を用い、forループが終わってからその変数の文字列をTextFieldインスタンスに加えればよい(スクリプト003)[*5]

スクリプト003■String型の変数にループ処理で文字列を加えた後TextField.appendText()メソッド使用

var test_str:String = "";
for (var i:int = 0; i<10; i++) {
  test_str += String(i);
}
test_txt.appendText(test_str);


[*4] ActionScript 2.0または1.0でも、ローカル変数はタイムラインに宣言した(タイムライン)変数よりアクセスが速い。したがって、関数内で配列エレメントをforループで処理する場合には、Array.lengthプロパティの値はローカル変数に納めておく方がよい。

[*5] String型を指定した変数には必ず初期値、とくに値がなければ空文字列""を設定しよう。String型変数のデフォルト値はnullなので、何も指定しないと文字列表現として"null"に変換されてしまう。

図004■String型変数のデフォルト値はnull


03 条件判定を考える
もっとも確実な最適化というのは、基本に戻って処理手順つまりアルゴリズムを磨くことだ。

たとえば、「うるう年かどうかを判定する関数」を定義して、引数に渡した整数の年がうるう年であればtrue、そうでなければfalseを返すことにしよう。うるう年は、つぎのように定められている。

    【うるう年の判定方法】
  1. 4で割り切れる年はうるう年。
  2. ただし例外として、100で割り切れる年は普通の年。
  3. さらに例外の例外として、400で割り切れる年はうるう年。

すると、うるう年を判定する関数isLeapYear()は、以下のスクリプト004のように定義することが考えられる(図005)。なお、剰余演算子%は、右側の項(オペランド[*6])で割った余りを求める。

スクリプト004■複数の条件を組合わせて判定する

function isLeapYear(nYear:int):Boolean {
  if ((nYear%4 == 0 && nYear%100 != 0) || nYear%400 == 0) {
    return true;
  } else {
    return false;
  }
}


図005■複数の条件をifステートメントに指定

関数はうるう年を正しく判定。ただし、条件がわかりくい。

しかし、条件をいくつも組合わせると複雑になりがちで、誤りを生みやすく、最適化のための分析もしにくい。if/else if/elseステートメントで条件を組合わせて指定するとそれらが順に判定され、最初にtrueと評価されたコードブロックを実行したらただちに処理を抜ける。

いわばテレビ番組の勝抜けクイズと同じで、1問でも正解すればその人はその場で解答者席から抜け、あとの問題には答える必要がない(図006)。すると、条件の順序が大切になる。

図006■条件判定の処理は勝抜けクイズと同じ

収穫したみかんを大きさによって仕分けるみかん選別機は、コンベヤーの先に大きさの異なる穴が空いている(図007)。そのとき、手前からS、つぎにM、そして最後にLの穴という順にすれば、正しく仕分けられる。

図007■みかん選別機の穴は手前から順にSML

条件は例外から考えると、整理しやすいことが少なくない。うるう年についても、例外の例外である400で割切れる場合から順に考えるとわかりやすい(スクリプト005)。

スクリプト005■例外から順に判定する

function isLeapYear(nYear:int):Boolean {
  if (nYear%400 == 0) {   // 400で割り切れる
    return true;   // 例外の例外のうるう年
  } else if (nYear%100 == 0) {   // 100で割り切れる
    return false;   // 例外の普通の年
  } else if (nYear%4 == 0) {   // 4で割り切れる
    return true;   // うるう年
  } else {   // 残り
    return false;   // ごく普通の年
  }
}

400で割切れれば迷うことなくうるう年だ。つぎの条件の100で割切れるというのは、例外で普通の年になる。これで例外はすべて勝抜けたので、あとは4で割切れるかどうかにより、うるう年と普通の年とを仕分ければよい。

もっとも、この仕分けは、あまり効率がよくない。最初に勝抜けるのは、400年に1度のうるう年だ。ざっと3/4を占めるであろうごく普通の年は最後まで残ってしまう。

処理効率を高めるには、できるだけ初めの方の問題で、より多くの勝抜けを出すべきだ(スクリプト006)。

スクリプト006■初めに多くの勝抜けを出す

function isLeapYear(nYear:int):Boolean {
  if (nYear % 4 != 0) {   // 4で割り切れない
    return false;   // ごく普通の年
  } else if (nYear%100 != 0) {   // 100で割り切れない
    return true;   // うるう年
  } else if (nYear%400 != 0) {   // 400で割り切れない
    return false;   // 例外の普通の年
  } else {   // 残りは400で割り切れる
    return true;   // 例外の例外のうるう年
  }
}

条件を否定形にしているので、少しわかりにくいかもしれない。けれど、仕分けを前掲スクリプト005とはちょうど逆の順序にしているだけだ。スクリプト005やスクリプト004と比べて、処理の効率は高まっている。

スクリプト004のように複数条件を論理演算子&&||で組合わせる場合にも、最適化は考えられる。評価が論理演算子の左辺(左オペランド)だけで判定できる場合、右辺(右オペランド)は評価されない。

if ((nYear%4 == 0 && nYear%100 != 0) || nYear%400 == 0) {

よって、スクリプト004におけるif条件の最初の論理式「nYear%4 == 0 && nYear%100 != 0」は、関数の引数nYearが4の倍数でなければ、論理積演算子&&の右オペランドである「nYear%100 != 0」は評価することなくfalseを返す。そして、つぎの論理演算子が論理和||なので、その右オペランド「nYear%400 == 0」がつぎに評価される。

また、逆に引数nYearが4の倍数であれば、つぎに論理積演算子&&の右オペランドが評価される。つまり、スクリプト004の条件の組合わせでは、少なくともふたつ以上の論理式を評価しなければ、条件判定ができない。それに対してスクリプト006は、ひとつの論理式の評価だけで、3/4近くを占める普通の年を勝抜けさせてしまう。

[*6]「オペランド」とは、被演算子を意味する(「ドット演算子と配列アクセス演算子」の注釈[*2]参照)。


04 visibleとalphaとremoveChild()
ステージ上に表示されたインスタンスを画面から消すには、3つのやり方が考えられる(表001)[*7]

    【インスタンスを画面から消すプロパティとメソッド】
  1. DisplayObject.visibleプロパティ
  2. DisplayObject.alphaプロパティ
  3. DisplayObjectContainer.removeChild()メソッド
表001■画面からインスタンスの表示を消すプロパティとメソッド
操作・機能 visible alpha removeChild()
描画負荷 なし 少しあり なし
再表示 簡単 簡単 面倒
サイズの情報 残る 残る 残らない
マウスイベント 受取らない 受取る 受取らない
表示リスト内のインスタンス 存在する 存在する 存在しない

●DisplayObject.visibleプロパティ
DisplayObject.visibleプロパティをfalseに設定すると、非表示になったインスタンスは画面の描画には負荷として加わらない。もとどおりに表示したいときは、プロパティ値をtrueに戻せばよい。

注意しなければならないのは、インスタンスが表示リスト内には存在し続け、そのサイズの情報も残ったままだということだ。たとえば、他のインスタンスとの当たり判定の領域には含まれるし、表示リスト内のインスタンスすべてを処理しようとすると非表示のインスタンスも対象となる。

なお、DisplayObject.visibleプロパティをfalseに設定したインスタンスは、マウスイベントを受取らない。

●DisplayObject.alphaプロパティ
とくに必要がある場合以外には、DisplayObject.alphaプロパティを使って画面から消すことは勧めない。なぜなら、アルファを0にしても、半透明のときとほどではないとはいえ、画面の描画負荷がかかるからだ。

インスタンスが表示リスト内に存在し続け、サイズ情報に反映されることはDisplayObject.visibleプロパティと変わらない。違うのは、DisplayObject.alphaプロパティを0にしても、マウスイベントを受取ることだ。つまり、透明ボタンができる。

もっとも、透明ボタンをつくるには、Sprite.hitAreaプロパティにヒット領域とするインスタンスを指定し、そのDisplayObject.visibleプロパティをfalseにしてしまう方法がある(図008)。このやり方であれば、描画負荷はかからない。

図008■Sprite.hitAreaプロパティに非表示のインスタンスを指定する

●DisplayObjectContainer.removeChild()メソッド
DisplayObjectContainer.removeChild()メソッドを使うと、インスタンスはStageオブジェクトを頂点とする表示リストから完全に削除される。描画に負荷もかからなければ、サイズの情報もなくなる。もちろん、マウスイベントも受取らない。

けれど、もとどおりに表示しようとすると、インスタンスを表示リストから削除する前の情報が必要になる。表示・非表示を頻繁に切替える必要がなく、当分インスタンスが不要になるという場合に用いるのが適切だ。

なお、3つのいずれのやり方でも、MovieClipインスタンスのフレームアニメーションは止まらない。無駄な処理をしないためには、原則として再生ヘッドは止めておくべきたろう。

[*7] 3つの手法の比較について詳しくは、Colin Moock「The Official "visible vs alpha vs removeChild()" Showdown」参照。


05 ArrayとVectorクラス
Arrayクラスは、複数の値を納めて扱うことができ、さまざまな場面で活用さる。そして、Flash Player 10では、配列と使い途の似たVectorクラスが実装された[*8]

●Arrayクラス
配列について注意しなければならないのは、第1に配列エレメントには型指定ができないことだ。したがって、型指定による最適化の恩恵を受けるには、取出したエレメントの値は型指定した変数に入れる必要がある。

第2に、配列エレメントは密(dense)すなわちインデックス0から連番で納められている方が、アクセスは速い[*9]。たとえば、つぎのようなArrayインスタンスを作成すると、インデックス0から2まではエレメントが密で、インデックス1000のエレメントは密ではない。

var my_array:Array = new Array();
my_array.push(0);
my_array.push(1);
my_array.push(2);
my_array[1000] = 1000;

すると、インデックス0から2までのエレメントの方が、インデックス1000のエレメントより速くアクセスできる。

my_array[2]   // アクセスが速い
my_array[1000]   // アクセスは遅い

●Vectorクラス
Vectorクラスのインスタンスには、配列と同じように複数の値を納めることができる。また、Arrayクラスと同じく、lengthプロパティやpush()pop()slice()sort()などのメソッドを備えている。

しかし、Vectorクラスはつぎのふたつの点で、Arrayクラスとは扱いが異なる。

    【配列と異なる点】
  1. エレメントにひとつのデータ型を指定。
  2. エレメントは密でなければならない。

Vectorクラスのコンストラクタは、つぎのようなシンタックスでインスタンスを生成する。

Vector.<データ型>(長さ:uint = 0, 長さの固定:Boolean=false)

Vectorクラスを使うとエレメントにデータ型が指定されるため、アクセスが配列と比べて高速になる。Vectorインスタンスの生成とエレメントの値の追加、変更、取出しは、つぎのように行う。

var myVector:Vector.<int> = new Vector.<int>();
myVector.push(0);
myVector.push(2);
myVector[1] = 1;
// myVector[5] = 5;   // インデックスが長さを超えるためエラー
var n:int = myVector[0];
// var my_str:String = myVector[1];   // データ型が一致しないためエラー

エレメントのデータ型がひとつで、連番のインデックスに値を納めるときには、ArrayよりVectorクラスのインスタンスを使う方がよい。また、ActionScript 3.0に新たに備わるメソッドには、引数として複数の値をVectorインスタンスで渡す場合が増えるだろう[*10]

[*8] 併せて「Vectorクラス」を参照。

[*9] 前出注[*2]「Tips for tuning ActionScript 3.0 performance for Flex and Flash developers」のPDFのp.14「Array Member Access」参照。

[*10] たとえば、Flash Player 10に実装されたGraphics.drawTriangles()メソッドは、つぎのように3つの引数をVectorインスタンスで指定する。

drawTriangles(頂点座標:Vector.<Number>, 頂点番号:Vector.<int> = null, uvtデータ:Vector.<Number> = null):void

06 数値の演算
数値演算にかぎらず、処理方法による速度の差は、環境によってばらつきが生じる。オペレーティングシステム(OS)、[ムービープレビュー]とブラウザ、さらにブラウザの種類により、ときには結果が逆転することさえある。

こうした違いは、FlashコンテンツがFlash Playerとブラウザを介して処理を行っていることに起因すると考えられる。したがって、細かな差にかかずらうより、処理手順やそのロジックを重視した方がよい。

●Mathクラスの数値演算
Mathクラスのメソッドの多くは、処理スピードがあまり速くない。たとえば、数値の2乗や絶対値の取得は、演算子で直接計算した方が速い(表002)。

表002■画面からインスタンスの表示を消すプロパティとメソッド
演算 Mathクラスによる処理 速い計算式
数値(n)の2乗 Math.pow(n, 2) n*n
数値(n)の絶対値 Math.abs(n) (n<0) ? -n : n

通常は、切捨てはMath.floor()メソッドを使う。しかし、数値を整数に変換するグローバル関数int()の方が、Math.floor()メソッドよりも処理は速い[*11]

●掛け算と割り算
ActionScript 3.0の数値演算では、割り算よりも掛け算の方が少し速い。たとえば、整数nに対するつぎのふたつの演算では、割り算より掛け算の方が速いという結果になる。

n / 5
n * 0.2

ただし、2の累乗による割り算はFlash Playerで最適化されているらしく、掛け算との差がほとんどなくなる。

もっともたとえば、つぎのような座標のイージング(イーズアウト)の処理では、減速率のパラメータは割り算にすると反比例の係数になり、演算値が双曲線を描いて変わる。掛け算であれば比例の直線的な変化なので、値の微調整がしやすい(図009)。

x += mouseX*0.2;

図009■比例と反比例のグラフ

●ビット演算
ビット演算は1と0の2進数に基づく演算で、CPUの処理に近いため、一般に高速とされる。10進数で数値全体をひと桁繰り上げれば10倍になり、逆にひと桁繰り下げれば1/10になる。ビット単位のシフト演算はこれを2進数で行うので、整数を2倍もしくは1/2にすることができる。

ビット単位の左シフト演算子<<は、左オペランドの整数(nNumber)を右オペランドの整数(n)の桁数左に繰上げる。2n(2のn乗)を掛け算したのと同じ結果になる。

nNumber << n

ビット単位の右シフト演算子>>は、左オペランドの整数(nNumber)を右オペランドの整数(n)の桁数右に繰下げる。2n(2のn乗)で割り算したのと同じ結果になる。

nNumber >> n

ビット単位の論理和演算子|は、ふたつのオペランドの対応する各ビット(2進数の各桁)の値の少なくとも一方が1であれば1を、そうでなければ0を返す(表003)。

表003■ビット単位の論理和演算子|の演算結果
オペランド 0 1
0 0 1
1 1 1

たとえば、Array.sortOn()メソッドの第2引数にソートオプションとして指定する複数のArray定数は、|演算子でまとめる。配列my_arrayをフィールド名field_strで、数値の降順に並べ替えたいときは、つぎのようなステートメントになる。

my_array.sortOn(field_str, Array.DESCENDING | Array.NUMERIC);

Array定数は次表004のとおり、2進数で表したとき値1を取る桁が重ならないようにフラグとして定められている。したがって、複数の定数値をまとめる場合、加算+で演算しても同じ結果が得られる。けれど、ビット単位の演算子を使う方が一貫している[*12]

表004■Array定数とその値
Array定数 10進数で表した値 2進数で表した値
CASEINSENSITIVE 20 = 1
0
0
0
0
1
DESCENDING 21 = 2
0
0
0
1
0
UNIQUESORT 22 = 4
0
0
1
0
0
RETURNINDEXEDARRAY 23 = 8
0
1
0
0
0
NUMERIC 24 = 16
1
0
0
0
0

カラー値は、RGB各成分値を0からFまでの16進数ふた桁の256階調で指定し、計6桁で表す。16進法はひと桁が24(=16)だから、ビット演算では4桁になる。したがって、16進法のひと桁繰上げ/繰下げは、ビット単位で4桁シフトすればよい。16進数がふた桁(162 = 24×2 = 28)の256階調なら、ビット単位の8桁シフトになる(図010)。

図010■256進法(階調)のひと桁はビット単位の8桁になる

すると、RGBの各256階調の値がint型の変数nR、nG、nBにそれぞれ入っているとすれば、RGGカラー値はつぎのようなビット演算で求めることができる。

var nColor:int = nR << 16 | nG << 8 | nB;
trace(nColor, nColor.toString(16));   // 確認用

●暗黙の型変換
整数の演算は変数をintまたはuint型で指定した方が処理は速くなる。しかし、int型の変数にNumber型の浮動小数値を代入すれば、数値のデータ型が黙示的に変換され、負荷はかえって増す。よって、数値の型指定は適切に行う必要がある。

[*11] ActionScript 2.0/1.0では、int()はFlash Player 5以降の使用が推奨されない古い関数だ。また、負の数の扱いが、Math.floor()メソッドやActionScript 3.0のint()関数とは異なる(F-site「int関数は整数を丸めない」参照)。

[*12] 詳しくは、「複数のフラグをひとつの整数で表す」を参照。

[おまけ] セッションで紹介したFlash CS4 Professionalの[ヘルプ]については、F-site「Flash CS4のヘルプ」を参照してほしい。


作成者: 野中文雄
イラスト: AYA
更新日: 2009年2月13日 Adobeデベロッパーセンターへの寄稿を追記。
作成日: 2009年2月3日


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