「FITC Tokyo 2009」詳細レポート

#4Grant Skinner氏「Quick as a Flash」ActionScriptの最適化

11月28日、ベルサール汐留にて、FITC Tokyo 2009が開催された。以下は、Grant Skinner氏のセッションのレポート。

Grant Skinner氏「Quick as a Flash」

ActionScript開発者なら、Grant Skinner(グラントスキナー)氏について説明する必要はないだろう。グラント氏のセッションでは、Flashの最適化について解説された。

写真1 kulerのサイトの最適化を行ったこともある、Grant Skinner氏
画像

氏は「最適化はどうディプロイメントするか、そしてCPU,メモリの負荷をいかに抑えるかにある。これからのAIRやモバイルの時代では、これらをますます重要視しなければならない」と、最適化の必要性を述べ、また「ベンチマークをしない最適化は意味がない」と語った。ベンチマークをする上での注意点として、"プラットフォーム・FlashPlayer・環境"を考慮してテストする必要がある。例として、FlashPlayer 9/10 IDE/10 pluginでNumeric(Number,int,uint)の処理スピードのテストを示した。

FlashPlayer 9では、uintが重く、intとNumberがほぼ同等という結果がでた。

写真2 FlashPlayer 9におけるベンチマーク(Numeric(Number,int,uint)の処理)
画像

FlashPlayer 10のIDE(Flash CSに搭載されているDebug Player)では、uintが一番早く、intが次に、そしてNumberが一番重かった。

写真3 FlashPlayer 10 IDEにおけるベンチマーク(Numeric(Number,int,uint)の処理)
画像

しかし、FlashPlayer 10のplugin(ブラウザに同梱されているRelease Player)では、uintとintがほぼ同等になり、Numberが一番重いことが分かったという。ここでは、uintとintが同等になったことよりも、Numberとの差が大きく開くことに、注意しなければならないと述べた。

写真4 FlashPlayer 10 pluginにおけるベンチマーク(Numeric(Number,int,uint)の処理)
画像

つまり、int/uintとの尺度差が大きくなるために、10のIDEで感じているNumberの処理速度よりも、10のpluginの重さのほうがより遅く感じるということだ。これによって、スキナー氏は「どのような環境で実行されるかというのを想定してテストすることが重要」とした。

ベンチマークには、数値で計量できるものとそうでないものがあると氏は語る。もっとも簡単にパフォーマンステストのひとつとして、"実行時間の計測"を解説。これは、getTimer関数を使用し、Flashが起動してからのミリ秒を取得するのもので、以下のようにベンチマーク開始前と終了後でgetTimer()を呼び出し、その差分を計ることで実行時間を計るというものだ。

t = getTimer(); 
// stuff to benchmark
elapsedTime = getTimer() -t;

この手法については、氏自身が制作したパフォーマンステスト用のフレームワークPerformanceTest.asが紹介された。また、"フレームレートの計測"も解説。getTimerを使用して、差分を計る点は実行時間の計測と同様だが、差分を計るタイミングを1フレーム後にし、そこで計測した値を1000で割ることでフレームレートを算出する。フレームレートの使いやすい計測ライブラリとして、Stats.asを紹介した。

コードの最適化には、シンタックスの最適化、アーキテクチャの最適化、アルゴリズムの最適化の三種類に大別できるとしている。これらは車のメンテナンスに例えることができ、シンタックスはチューニングに、アーキテクチャはパーツの変更・修理に、そしてアルゴリズムはエンジンに置き換えることができるとし、大きくなればなるほど、相応のコストが掛かるとしている。氏は「パフォーマンスをよくするには最高のエンジンをチューニングをしっかりすることが重要」とし、大枠の実装を最適化するだけでなく、細かな部分の最適化と併用することで、真のパフォーマンスを発揮できることを説明した。

次に、コードの最適化に関する具体的な方法を解説。紹介されたtipsについて、いくつかをリストにした。

シンタックスの最適化例
  • 毎回同じ計算をする場合は、予め計算し、値を保持しておく
  • 乗算は除算よりはやい
  • 厳密な型指定を使う
  • ビット演算をMath.floor,Math.round代わりに使用する
  • &&で判定するときはコストがかからない方を先に
  • クラスメソッドよりも、インスタンスメソッドのほうが早い
  • ループ内で変更されないプロパティは参照からはずす(例:配列のlengthをローカル変数に保持しておく)
アーキテクチャの最適化例
  • インスタンス化の削減・再利用
  • ディープアクセス(例:Hoge.getInstance().hoge().hogeChild().hoge.x)は負荷が掛かる
  • 演算子の数を削減
  • メソッドの呼び出しは時間が掛かる。パフォーマンスがシビアの場合にはインラインにしてしまうのも一つの手である
  • dispatchEventよりもcallBackのほうが軽い。ただ、パフォーマンスが重要でない場合にはdispatchEventのほうがよい
  • Arry Object Dictionary Vecotor LinkedList では固定長Vectorが早く、LinkedListが最も重い

アルゴリズムの最適化についての解説では、アルゴリズムの選択でまず必要なのは"スピード"よりも、"やりたいことができるのか"ということ。そして、アルゴリズムの成長率であるという。成長率とは、繰返し処理内でのロジックで、実行から時間が経てば経つほど負荷が増大に成長するということで、対数的に上がっていくかもしくはコストが下がっていくのが理想的だと解説した。氏は「多くの本でアルゴリズムが解説されており、多くのライブラリが公開されている。だが、重要なのは自分がそれをどう適用できるかということで、そのライブラリの中には必要のない関数もあるかもしれない。その場合にはそれを除くことが必要だ」と述べる。

氏によれば「最適化はコードだけに限ったことではない」という。ビデオ、オーディオのデコード、さらにフレームのグラフィックレンダリングやフィルターの適用には、処理に時間を食うことになると説明。また、ガベージコレクションに関しても念頭に置いておかなければならないとし、⁠コードの最適化と含め、これら全体をみていく必要がある」と述べた。また、グラフィックの最適化についてのいくつかのtipsを紹介した。氏によれば、"黒魔術的"としている。以下にそのいくつかをリストにした。

グラフィックの最適化例
  • 線をシンプルにする
  • マスクを避ける
  • グラディエーションをシンプルにする
  • フォントのアンチエイリアスを切る
  • ベクターはビットマップに変換する
  • DispalayObject.zを使用することでビットマップに変換され、キャッシュが適用されるようになる
写真5 グラフィックの最適化例でのデモ
画像

最後に、ガベージコレクションに関するtipsも紹介した。氏によれば、⁠ガベージコレクションは自動で行われるが、CPUが集中的に使っている場合にはガベージコレクションは後回しにされてしまう。」という。このため、どこかの段階で全てを止め、ガベージコレクションが実行された後にまた再実行するとよいとアドバイス。また、ガベージコレクションを極力減らす方法として、オブジェクトの再利用を提案。オブジェクトプールという手法を紹介した。これは、オブジェクトを使い終った場合、削除するのではなく配列に格納しておき、再度必要になった場合にはそのオブジェクトを取り出すといったもの。また、使っていないリスナーやタイマーは全てクリーンアップしておかなければならないと解説。これらはリスナー登録されている場合、ガベージコレクトの対象にならないからだ。これを氏が簡単に実装したライブラリ、Janitor.asも紹介された。

//オブジェクトプール
function getThingInstance(param){
  if(pool.length){o=pool.pop();}
  else {o=new Thing();};
  o.init(param);
  return o
}

このセッションでは、Flashの最適化について、かなり奥深く学ぶことができた。

なお、このスライドは公開されているので、是非チェックしておくとよいだろう。

おすすめ記事

記事・ニュース一覧