ActionScript 3.0で始めるオブジェクト指向スクリプティング

第56回 【特別編】配列エレメントすべてをforループで扱う

この記事を読むのに必要な時間:およそ 3.5 分

技術評論社より2月10日に発売された拙著ActionScript 3.0パフォーマンスチューニングから,ActionScript 3.0の最適化の仕方をご紹介する特別編の2回目は,配列エレメントすべてをforループでいかに速く扱うかだ。でも,この特別編の1回目(第54回に配列よりVectorクラスを使えといったではないか,と訝る向きもあろう。そのとおりだ,Vectorオブジェクトが使えるなら,使ったほうがよい。

だが,Vectorクラスを使うためには条件があった(第54回「【特別編】配列の処理をVectorオブジェクトで最適化する」Vectorクラスのおさらい参照)。Flash Playerのバージョンも10以降でなければならない。そのため,配列を用いるしかない場合がある。それに,Vectorクラスで使えるテクニックは,文法の違いを除いて,ほぼ配列でも通用する。しかし逆に,配列にはVectorクラスとは違ったコツがある。したがって,今回は配列をお題とする。

配列エレメントをforループで取出す

まずは,配列エレメントをforループで取出して操作する場合だ。もっとも,配列の扱いやforループで気をつけるべきことは,これまでの連載で説明してきた。そこで,つぎのスクリプトを見て,最適化するにはどこを直したらよいか考えてほしい。配列(my_array)に納めたエレメント(my0_mc~my2_mc)はMovieClipインスタンスだ。ただし,前述のとおりVectorオブジェクトではなく,配列を用いるものとする。

var my_array:Array = [my0_mc, my1_mc, my2_mc];
for (var i:Number = 0; i < my_array.length; i++) {
  my_array[i].x = 10;
  my_array[i].y = 10;
}

このスクリプトは配列に納めたMovieClipインスタンスをforループですべて取出して,xy座標を定めている。文法的に何の問題もなく,すべてのインスタンスの位置が正しく決まる。しかし,処理を速める余地が3つある。

第1は,forループで用いるカウンタ変数(i)の型指定だ。Number型は小数値を含む(「浮動小数点数」という)。だが,カウンタには整数を使う。しかも,配列インデックスとしても用いるなら,マイナスにはならない。そうした場合には,同じ数値でもuint型で宣言する方がよい。

第2に,forステートメントの継続条件に用いられているArray.lengthプロパティの参照だ。継続条件は,繰返す処理がひとつ終わるごとに確かめられる。つまり,ループ回数分参照されることになる。こういう値は予め変数に変数に入れて,それを参照する方が速い。とくに,関数の中でローカル変数にとると,さらにアクセスは速まる。

第3は,配列のMovieClipインスタンスを変数に取出していないことだ。一般に,配列アクセス演算子[]で同じエレメントを幾度も参照するより,変数に入れてしまった方がアクセスは速い。これは前述第2と同じ考慮といえる。加えて,配列エレメントには,Vectorオブジェクトと違って型指定できないことを思い出してほしい。ActionScript 3.0は,型指定されたデータはメモリや処理を最適化して扱う。配列エレメントを型指定した変数に納めなければ,この最適化の恩恵が受けられないのである。

配列エレメントを取出すforループの最適化
  • 配列インデックスとカウンタ変数は整数型(int/uint)で指定
  • 継続条件に用いるArray.lengthプロパティは予め変数にとる
  • 取出したエレメントは型指定した変数に入れる

これら3つの修正を加えて書直したのが,つぎのフレームアクションだ。変数varとその型指定をしっかり使うことが大切になる。

var my_array:Array = [my0_mc, my1_mc, my2_mc];
var nLenght:uint = my_array.length;
for (var i:uint = 0; i < nLength; i++) {
  var my_mc:MovieClip = my_array[i];
  my_mc.x = 10;
  my_my.y = 10;
}

配列エレメントをforループで加える

つぎは,配列にエレメントを加える場合だ。もちろん,変数と型指定をしっかりすべきことは,前項と変わらない。それに加えて,最適化を考えるときに知っておきたいことを紹介しよう。また,課題のスクリプトを先に掲げる。0から9までの連番整数をエレメントとする配列が,forループの処理でできあがる。

var my_array:Array = new Array();
for (var i:uint = 0; i < 10; i++) {
  my_array.push(i);
}

これは難問だろう。ActionScript 3.0の教科書に出てきてもおかしくはないスクリプトだ。もし,筆者がこの処理に問題があるかと問われたら,ないと答える。だが,少しでも速いタイムをたたき出したいとするなら,ふたつ手が加えられる。答えも先に出してしまうことにする。

配列エレメントを加えるforループの最適化】
  • 配列インスタンスは配列アクセス演算子[]でつくる
  • 配列エレメントは配列アクセス演算子[]で加える
var my_array:Array = [];
for (var i:uint = 0; i < 10; i++) {
  my_array[i] = i;
}

これを見て,配列アクセス演算子[]は処理が速いのかと思うかもしれない。結果だけならそう覚えてもよい。けれど,理解としては正しくない。配列アクセス演算子[]の処理そのものは,別に速くないからだ。配列アクセス演算子[]を使うとなぜ速いのか知るには,Flash Playerの立場で考えなければならない。ほとんどの読者はFlash Playerの立場など想像したこともなかろう。これを機会にちょっと考えてみてほしい。

コンストラクタよりもリテラル記述が速いのは

メソッドからインスタンスを得ることなく,プログラムに直接記述される値は「リテラル」という。new演算子でArray()コンストラクタメソッドを呼出すより,配列アクセス演算子[]で配列をリテラルで書く方が速いのは,Arrayクラスのコンストラクタがオーバーロードされているからだ。

「オーバーロード」(多重定義)というのは,同じ名前のメソッドが複数定められていることを意味する。[ヘルプ]で[Array]クラスのメソッドを調べると,実際コンストラクタメソッドがふたつある図1)。このふたつをどうやって使い分けるかというと,コンストラクタに渡される引数が鍵になる。

図1 Arrayクラスのふたつのコンストラクタメソッド

図1 Arrayクラスのふたつのコンストラクタメソッド

Array()コンストラクタメソッドには,引数がいくつでも渡せる。そして,多くの場合それらの引数は,新たにつくられる配列のエレメントとして納められる。しかし,整数をひとつだけ渡すと,もうひとつのコンストラクタが呼出されて,引数値の長さ(エレメント数)の配列がつくられる表1)。

表1 コンストラクタメソッドの引数によってつくられる配列の違い

コンストラクタ呼出し引数の意味つくられる配列
new Array()エレメント[]
new Array(3)長さ[undefined, undefined, undefined]
new Array(0, 1, 2)エレメント[0, 1, 2]
new Array("a")エレメント["a"]

さあ,ここでFlash Playerの立場で考えてみよう。コンストラクタが呼出された場合,まず引数の数を確かめなければならない。複数ならそれらはエレメントだ。しかし,引数がひとつだったら,さらにそれが整数かどうかを見る。整数なら引数は長さ(エレメント数)の指定,そうでなければエレメントがひとつ渡されたことになる。このように確かめていって初めて引数の意味が定まり,どちらのコンストラクタを呼出せばよいのかも決まる図2)。

図2 引数の数とデータ型でその意味と呼出すコンストラクタが決まる

図2 引数の数とデータ型でその意味と呼出すコンストラクタが決まる

それに対して,配列アクセス演算子を用いてリテラルで書くとき,演算子[]の中に加えられるのはエレメントだけだ。値が何を意味するのか確かめる必要がない。このひと手間の差が,処理の速さにつながるのだ。

著者プロフィール

野中文雄(のなかふみお)

ソフトウェアトレーナー,テクニカルライター,オーサリングエンジニア。上智大学法学部卒,慶応義塾大学大学院経営管理研究科修士課程修了(MBA)。独立系パソコン販売会社で,総務・人事,企画,外資系企業担当営業などに携わる。その後,マルチメディアコンテンツ制作会社に転職。ソフトウェアトレーニング,コンテンツ制作などの業務を担当する。2001年11月に独立。Web制作者に向けた情報発信プロジェクトF-siteにも参加する。株式会社ロクナナ取締役(非常勤)。

URLhttp://www.FumioNonaka.com/

著書

コメント

コメントの記入