AS3のインライン展開機能まとめ

By tonNo Comments

ちょっと前にFlash Builder 4.7 betaが公開されましたが、このFB4.7には次世代コンパイラであるActionScriptCompiler 2.0 (ASC2.0)が組み込まれています。

ASC2.0は高速化であったり、より仕様に厳格なコンパイルをするようになったり、色々な変更点があるのですが、今回はASC2.0からの新機能であるインライン展開機能についてまとめていきます。

※この記事はFlash Builder 4.7 beta時のものです。正式版では異なる動作をする場合があります。

10/15 追記

FxUG名古屋勉強会でASC2.0について発表しました
本記事はASC2.0 Preview1時点の情報ですが,スライドではPreview3時点の情報をまとめています
こちらのほうが情報が新しいので参考にしてください
http://ton-up.net/blog/archives/1176

インライン展開って?

インライン展開ってなんぞや?って人に簡単に説明

インライン展開 – Wikipedia

インライン展開は、簡単に言うと、本来関数を呼び出している場所に、関数の中身をそこに書いちゃうっていう機能です。

変数aに10を代入するだけのhogeっていう関数があるとします。

function hoge():void{
    this.a = 10;
}

それをtest関数で呼び出すコードがあります。

インライン展開前

function test():void{
    hoge();
}

hoge関数をインライン展開すると、下のようになります。

インライン展開後

function test():void{
    this.a = 10;
}

こんな感じで、関数を呼び出している場所に関数の中身を埋め込んじゃいます。
インライン展開することで、関数呼び出しのオーバーヘッドがなくなるため、高速化が期待できます。
関数呼び出しオーバーヘッドがかなり大きいas3では特に有効になるのではないかと思います。

インライン展開される条件

しかし、どんな関数でもインライン展開してくれるわけではありません。
インライン展開させるためには、少し知っておかなければいけないことがあります。

-inline コンパイラ引数

インライン展開させるためには、まず -inline という文字列をコンパイラ引数に追加してください。
これがないと絶対にインライン展開してくれません。

[Inline] メタタグ

関数宣言の直前に [Inline] メタタグを書くことで明示的にインライン展開を宣言することができます。
しかしこのタグをつけたからといって必ずインライン展開されるわけではありません。
他の条件を満たす必要があります。

関数は必ず[Inline]タグをつけないとインライン展開されませんが、getter/setterでは、このタグをつけなくとも他の条件を満たせば暗黙的にインライン展開されます。
暗黙的と言っても -inline コンラパイラ引数の追加は絶対ですが。

//getter/setterは[Inline]タグがなくてもインライン展開可能
final public function get hoge():Number{
	return _hoge;
}

//[Inline]タグがないのでインライン展開できない
final public function cannotInline():void{
	trace("この関数はインライン展開されません");
}

//インライン展開可能
[Inline]
final public function canInline():void{
	trace("この関数はインライン展開できます");
}

final/static修飾子をつける、またはグローバル関数であること

上の例でも既に出ていますが、インライン展開させたい関数/アクセサには必ずfinal修飾子かstatic修飾子をつけなければいけません。
元々staticである場合以外はfinal修飾子をつけることになると思います。

クラスをfinalにしたら全部の関数がfinal扱いされてインライン展開できるんじゃね?
と思ったりもしますが、クラスをfinalにするだけではインライン展開できません。関数をfinalにしてください。

//final/static修飾子がないのでインライン展開できない
public function get hoge():Number{
	return _hoge;
}

//[Inline]タグをつけてもfinal/static修飾子がないのでインライン展開できない
[Inline]
public function cannotInline():void{
	trace("この関数はインライン展開されません");
}

//static修飾子があるのでインライン展開可能
[Inline]
static public function canInline():void{
	trace("この関数はインライン展開できます");
}

また、グローバル関数はクラス内ではないためfinal/static修飾子が使えないので[Inline]タグを付けるだけでインライン展開可能になります。
setter/getterならば[Inline]タグを付ける必要もありません。そんなことをするかどうかは別としてw

package net.ton_up.test {
	[Inline]
	public function globalTest():void{
		trace("インライン展開できます");
	}
}

ネストされた関数を持ってはいけない

正確には
・他のアクティベーションオブジェクトを持ってはいけない
・関数クロージャを持ってはいけない
ということらしいですが、要するにネストされた関数を持ってはいけない、ということだと思います。
(自信ないので間違っていたら教えてください)

アクティベーションオブジェクトという単語は聞きなれないですね。僕もですw
アクティベーションオブジェクトについては僕もまだよくわかっていないので、以下を参考にしてください。
Adobe Flex 3 ヘルプ 関数スコープ
アクティベーションオブジェクトとスコープチェーン | www.imajuk.swf

//インライン展開できません
[Inline]
final public function cannotInline():void{
	var aa:Number = 10;
	
	var f:Function = function():void{
		trace(aa);
	}
	
	f();
}

//これもダメです
[Inline]
final public function cannotInline2():void{
	function buta():void{
		trace("インライン展開できないよ");
	}
	
	buta();
}

private function methodHoge():void{
	trace("ただのメソッドです");
}

//他のメソッドを呼ぶだけならインライン展開可能
[Inline]
final public function canInline():void{
	trace("インライン展開できます");
	methodHoge();
}

try-catch文を含んではいけない

[Inline]タグをつけて、final修飾子をつけても、関数の中にtry-catch文があるとインライン展開されません。

//try-catchを持つ関数はインライン展開できません
[Inline]
final public function tryTest():void{
	try{
		throw new Error();
	}catch(e:Error){
		trace(e.getStackTrace());
	}
}

with文を含んではいけない

try-catch文と同じように、with文を含む関数もインライン展開されません。

//with文を持つ関数はインライン展開できません
[Inline]
final public function tryWith():void{
	with(this){
		graphics.beginFill(0x0077ff);
		graphics.drawCircle(10, 10, 50);
		graphics.endFill();
	}
}

式の数が50以下であること

式の数が50以下であること、とありましたが色々試した結果、コンパイルした際のActionScriptByteCode(abc)の命令の数が50以下であること なのではないかと思います。

なかなかコードを書いているときにabcの命令を想像するのは難しいですが、一個例を挙げると、このくらいで50です。

//インライン展開可能なギリギリな命令数
[Inline]
final public function try50():void{
	var n:Number = 0;
	n = 1;
	n = 2;
	n = 3;
	n = 4;
	n = 5;
	n = 6;
	n = 7;
	n = 8;
	n = 9;
	n = 10;
	n = 11;
	n = 12;
	n = 13;
	n = 14;
	n = 15;
}

まぁインライン展開させたい関数はできるだけ短くしてねってことです。

サブクラス/インターフェース経由で呼び出さない

サブクラスまたはインターフェース経由で呼び出した場合はインライン展開されません。

例えば、以下の様なインターフェースIInlineとそれを継承したExInlineがあるとします。

package{
	public interface IInline{
		function hoge():void;	
	}
}
package{
	public class ExInline implements IInline{
		[Inline]
		final public function hoge():void{
			trace("インライン展開される?");
		}
	}
}

以下の様にExInlineクラスをインターフェース型に格納して呼び出した場合はインライン展開されなくなります。

//これはインライン展開されます
var test2:ExInline = new ExInline();
test2.hoge();

//これはインターフェース経由で呼び出しているのでインライン展開されません
var test:IInline = new ExInline();
test.hoge();

以上がインライン展開するための条件です。
(他にも条件があったら教えて下さい)

めっちゃ多いじゃねえか!って思われると思いますが、実際にインライン展開するような関数は中身の処理に比べて関数呼び出しのーバーヘッドの比率が大きいものでないと効果はないので、そんなに複雑な処理はしてないと思われるのであまり問題はないでしょう。

実際に気をつけないといけないのは
・-inline コンパイラオプション
・[Inline]タグ
・final/static修飾子
・サブクラス/インターフェース経由で呼び出さない
くらいじゃないかなーと思います。

安心してください、-inlineオプションと[Inline]タグさえつけておけば、インライン展開させたいのにできない状況ではリアルタイムでエラーを表示してくれます。そう、FB4.7ならね!
(実際は結構わかりづらいエラー表示の仕方してくれたりしますけど・・・・)

インライン展開の効果

インライン展開がどのくらい効果があるのか、実際に計測してみました。
実験に使用したのは以下のコードです。


package {
import flash.display.Sprite;
import flash.events.Event;
import flash.events.MouseEvent;
import flash.text.TextField;
import flash.utils.getTimer;

public class InlineTest extends Sprite{
private static const CALC_N:int = 10000000;
private static const TEST_N:int = 10;

private var _inlineN:Number = 10.5;

private var tf:TextField;

public function InlineTest(){
init();
trace(“click to start”);
stage.addEventListener(MouseEvent.CLICK, function(e:Event):void{
stage.removeEventListener(MouseEvent.CLICK, arguments.callee);
tf.text = “”;
test();
});
}

/**
* インライン展開されるgetter
*/
final private function get inlineN():Number{
return _inlineN;
}

/**
* inlineNをCALC_N回呼び出してその時間を計測します
*/
private function calcTime():Number{
var time:Number = getTimer();

var n:Number = 0;
for(var i:int = 0; i < CALC_N; i++){ n += inlineN; } return getTimer() - time; } /** * calcTime()をTEST_N回呼び出して平均を出します */ private function test():void{ var sum:Number = 0; for(var i:int = 0; i < TEST_N; i++){ var time:Number = calcTime(); sum += time; trace((i+1) + "回目 : " + time + "ms"); } trace("平均 : " + (sum/TEST_N) + "ms"); } private function init():void{ stage.scaleMode = "noScale"; stage.align = "TL"; stage.color = 0xcccccc; tf = new TextField(); tf.autoSize = "left"; addChild(tf); } private function trace(str:String):void{ tf.appendText(str + "n"); } } } [/as3] 何をしているかというと、単純なgetterをfor文で10000000回呼び出して、その時間を計測します。 それを10回繰り返して平均を出しているだけの簡単なサンプルです。 これをコードは一切変えずに-inlineオプションをつけた時とつけない時でどのくらい差が出るか測ります。 flash領域をクリックすると始まります。 少し固まりますが計算中なのでしばらくお待ち下さいw まず-inlineオプションをつけない場合

This movie requires Flash Player 9.0.0
次に-inlineオプションをつけた場合
This movie requires Flash Player 9.0.0
どうでしょうか? 自分の環境では約10倍の差が出ます。 実際にはこんなにfor文を回すことはないでしょうが、単純なgetter/setterでは、中身の処理に比べてかなり関数呼び出しのオーバーヘッドが大きいので、大きいforを回す際のインライン展開は十分高速化が期待できると思います。

バグ

高速化が期待できるインライン展開ですが、FB4.7 betaのASC2.0にはバグが存在します。

for文の中でsetterをインライン展開すると
VerifyError: Error #1030: スタックの深さがアンバランスです。1 != 0。
というエラーが発生します。

以下のコードで再現できます。


package {
import flash.display.Sprite;
public class InlineTest extends Sprite{
public function InlineTest(){
for(var i:int = 0; i < 1; i++){ hoge = 1; } } private final function set hoge(value:Number):void{ trace(value); } } } [/as3] betaなのでバグはしょうがないですし、正式版では直るでしょうが、テストで使う場合には注意してください。 解決策を知っている方は教えていただけると助かります。

9/12 15:30 追記

——————————
このバグはAKABANAさんにバグ報告していただいていましたが、Adobeの中の人から、現在修正中です、との返事が返ってきました。
Adobe Forums: ASC 2.0 : Inlined Setter Bug Reports

AKABANAさん、ありがとうございます。
——————————

9/12 16:00 更に追記

——————————

とのことです。自分でも試しましたが、通常の関数呼び出しても同様の現象が再現できました。
thisをつけると回避できることも確認できました。

またまたAKABANAさん、ありがとうございます!
——————————

以上、インライン展開機能のまとめでした!

FB4.7+ASC2.0はかなり期待できるので、他の機能についてもまとめていきたいと思います。
(Monocleもはやくきて!)

参考

Introducing ASC 2.0 – ByteArray.org
ASC2.0 のインライン化機能 (新しい ActionScript コンパイラの変更点つづき) – akihiro kamijo
Adobe Forums: Forum: AIR 3.4


AS3, Flash

Leave your Comment

メールアドレスが公開されることはありません。

Blue Taste Theme created by Jabox