nazolabo

フリーランスのWebエンジニアが近況や思ったことを発信しています。

AS3処理の疑問

Math.abs()を使わず絶対値を求める


Absolute value


//version 1
i = x < 0 ? -x : x;

//version 2
i = (x ^ (x >> 31)) - (x >> 31);


この単純なコードでなんと2,500%高速化。さらにビット演算を組み合わせるとさらに加えて20%高速化。

http://actionscript.g.hatena.ne.jp/ConquestArrow/20070621/1182359767

これがちょっと気になったので調べてみた。

パターン

  • ケース1(単純にMath.abs)
b = Math.abs(a);
  • ケース2(三項演算子)
b = a < 0 ? -a : a;
  • ケース3(if)
if (a < 0) {
  b = -a;
} else {
  b = a;
}
  • ケース4(ケース2を外部関数化)
private function abs(a:int):int {
    return a < 0 ? -a : a;
}

b = this.abs(a)
  • ケース5(if+乗算)
if (a < 0) {
  b = a * -1;
} else {
  b = a;
}
  • ケース6(bit演算)
b = (a ^ (a >> 31)) - (a >> 31);

結果

上記のをそれぞれ10000000回回した結果(a=-3)(PentiumM 1.1GHz)

ケース1(単純にMath.abs) 2912ms
ケース2(三項演算子 342ms
ケース3(if) 343ms
ケース4(ケース2を外部関数化) 2216ms
ケース5(if+乗算) 316ms
ケース6(bit演算) 113ms

結論

  • 関数呼び出しはものすごく遅いので、速度が必要な場面ではインライン展開すべき(inline関数とかあれば楽だけど…)
  • bit演算は可読性が極端に落ちるので、劇的に速度向上が期待できる場面以外では使わないほうがいい
  • 三項演算子とifは大差ない(おそらく内部的には同じだと思われる)ので、可読性と好みで判断する

こういう高速化は、「何故そういう記述で高速化できるのか」がわかってないと、適切に利用できないので、正しく検証して利用したほうがいいと思います。逆に言えば、それがわかっていると、他の場面でも応用が利くと思います。
あとbit演算に感動した人は、ゲームプログラミング関係で調べるといろいろ出てきます。たとえばxor swapネタならこのあたりとか。(ちなみに加減算swapもオーバーフローする値でおかしくなる可能性があるので、swap自体はよほどの理由がない限り一時変数を使うのをお勧めします)

DisplayObjectの座標にNaNを突っ込むとWinとMac/Linuxで挙動が違う

package {
    import flash.display.*;  
    
    public class nantest extends Sprite {
        
        [Embed(source='pict.gif')]
        private var Pict:Class;
        
        public function nantest() {
            var v:Number;  // NaN
            
            var item:Bitmap = new Pict();
            item.x = v;
            this.addChild(item);
        }        
    }   
}

こんな感じのソースを動かすと、Windowsでは表示されるけど、Mac/Linuxでは表示されない。
テスト用swf:http://highfreq.net/swf/nantest.swf
そもそもNaNが突っ込まれてる時点でバグなんだけど、Windowsだけ挙動が違うってのがはまりそうなのでメモ

AS3のBitmapData描画速度比較

AS3のBitmapDataの通常矩形描画手法をいろいろ計ってみた。32x32のBitmapDataを10000回ループで描画して調査。
結果(PentiumM 1.1GHzにて)

  • copyPixels:104 (ms)
  • getPixel/setPixel:3551 (ms)
  • getPixels/setPixels:1578 (ms)
  • draw:426 (ms)

ソース:http://highfreq.net/dev/speedtest.zip
これからわかること

  • drawはcopyPixelsの約4倍遅い
  • getPixelで1px毎に描画するのは絶望的に遅い(まあ当たり前か)
  • どうしても1px毎の描画が必要な場合は、あらかじめgetPixelsで全領域を取得しておいて、そのByteArrayに対して操作し、最後にsetPixelsで上書きする。
  • 回転処理はcopyPixelsで部分領域を取得したあとにdrawがいい

まあdrawはネイティブコードだと思うから、AS上で動かすよりは速いってのは当たり前だよなー。自前描画はよほどのことがない限りしないほうがいいかも。
試したソースが前エントリのライブラリを使ってるからオーバーヘッドも結構あるけど、そこまで極端に変わるもんじゃないと思う。
ところで10000回ループでこの速度ってことは、1/60フレーム中にcopyPixelsなら約1600回、drawなら約400回描画が可能という計算になる。まあ実際はロジック部分が入るからそうはいかないけど。20x15(+1x1)マップチップの描画は大体336回=3.36msかかるという計算。なかなか厳しい気がする。30フレームが無難か。

AS3でブラウザの画面サイズにswfの領域を合わせる

stage.scaleMode = StageScaleMode.NO_SCALE;
stage.align = StageAlign.TOP_LEFT;
stage.addEventListener(Event.RESIZE, function(evt:Event):void {
this.width = stage.stageWidth;
this.height = stage.stageHeight;
});

stageWidth/stageHeightに気づかないでwidth/heightを調べててはまってた…
#領域自体は勝手に拡大されるので、RESIZEを取る必要は実際はあまり無いかもしれません

AS3でゲームっぽいものを作ってみるテスト

http://highfreq.net/swf/sampleApp/sampleApp.swf
ソース:http://highfreq.net/dev/sampleApp.zip
ASで非イベントドリブン的な、ひたすらBitmapDataで描画するようなライブラリを作るテスト。ついでにシーン管理付き。
ってかBitmapData::drawとかBitmapData::copyPixelsで上下左右反転ってどうやるの?
あとBitmapData::drawが使えなさすぎる。clipRectが本当にclipRectっていうか、行列変換した結果からクリップするってのはある意味正しいんだけどキャラチップ画像から部分的に抜き出して回転とかが全くできない。一度別のBitmapDataに移す必要がある。自前で実装するのとどっちが速度が出るかそのうち試す。copyPixelsは高速だと信じたいので、反転ができなければあらかじめ反転画像を用意する方法も考える。
ソースは煮るなり焼くなりご自由に。ただし仕様が変わる可能性大なので拡張する場合は自力でどうにかしてください。名称も確実に変わります。真面目に作る気があれば。

サクラエディタ用ActionScript3キーワードファイル

http://highfreq.net/dev/ActionScript.kwd
サクラエディタな人は便利。
入力補完ファイルは
http://highfreq.net/dev/ActionScript_autocomplete.kwd
キーワードファイルと入力補完ファイルの違いは

  • 入力補完ファイルにはパッケージ指定がある(flashとかmxとか入れてCTRL+SPACEを押すとパッケージ名の補完が可能)
  • キーワードファイルには型とかがある(入力補完では不要と判断した)

ってところ。

mxmlcでビルドしたswfでローカルファイルを読む時の注意

コンパイルオプション(xxx-config.xml)で、

    <use-network>false</use-network>

を設定する必要がある。
trueだと、リモートファイル(HTTPで読み込む)は読み込めて、ローカルファイル(file://)は読み込めない。
falseだとその反対になる。
詳しくはセキュリティサンドボックスを参照。
テストの時はfalseで、本番はtrueにするという使い方になるのかな。