2008年03月15日

Shibuya.abc#1

しばらくさぼったままでしたが、久しぶりに更新します。

3月15日に、サイボウズ・ラボのオフィスでShibuya.abc#1が開催されました。
私もFlashLiteむけSWFの最適化の話をお話しさせていただきました。
ABC(ActionScript Byte Code)に関連する濃い話をいろいろ聞けて楽しかったです。

私の発表資料は、ここからダウンロードすることができます。
ファイルをダウンロード

勉強会の動画は、ニコニコ動画で見ることができるようです。西尾さん、ありがとうございます。
Shibuya.abc#1


この動画のダウンロード:一般エコノミープレミアム Posted with NicodavGen on 2008.03.17


2007年07月12日

Flash: コールスタック

FlashのVMはスタックマシンなので、たとえば演算をする場合は引数をスタックに積んでから演算命令を実行します。この用途で使われるスタック(ここでは値スタックと呼ぶことにしましょう)のほかにも、FlashのVM内で使われているスタックがあります。それは、Function CallやFrame Callをしたときに戻ってくる場所を保持しておく、コールスタックです。
おなじ「スタック」ですが、VM内ではまったく別の場所で独立に管理されているようです。 ですから、コールする側が値をスタックに積んでおいて、コールされた側でその値を読み出したり、 逆に、コールされた側で値をスタックに積んで、Returnした後にその値を読み出す、といったことができます。
以下を実行すると、
function foo () {
  __bytecode__("2696040000420003");
  // trace
  // push "B", undefined
  // pop
}

__bytecode__("96040000410003");
// push "A", undefined
// pop
foo()
__bytecode__("2696010003");
// trace
// push undefined
// pop
画面に、
A
B
が表示されます。foo()内のtraceで、呼び出す前に積んでおいたAが、 foo()から帰ってきた後のtraceでfoo()の中で積んでおいたBが使われます。 関数呼び出しではなく、フレーム呼び出しの場合にも、同様のことができます。
上でちょっとわかりにくいのは、__bytecode__命令で毎回最後にundefined(0x03)をpush しているところかもしれません。 __bytecode__は、x = __bytecode__("...")のようにひとつの値を返す関数のように扱われるので、 値を使わない場合は自動的にpopが挿入されてしまいます。 これによってスタックに積んだ値が捨てられてしまうことがないように、undefinedを pushしているのです。
関数呼び出しの引数や返り値の受け渡しには実際にこれと似たようなことが行われています。 ただし、引数についてはローカル変数へのバインドを行うところが違います。
上に述べたことを応用すると、 フレーム呼び出しでも返り値の受け渡しをスタック経由で行える、ということになります。 ActionScriptの文法の枠からは外れてしまいますが。

2007年06月15日

FlashLite: バイトコードもいじってコードサイズを減らす

コードサイズ節約ねたの続きです。 アクションスクリプトの工夫だけではどうにもならない場合に、SWFのバイトコードを書き換えるような最適化をすればさらにコードサイズを節約できることもあります。 SWFのバイトコードを書き換えるには、perlのSWF::Parserやflasmを使う方法があるようです。私はSWF::Parserに付属しているdumpswf.plxを使うことが多いです。 バイトコードを書き換えるような最適化には、たとえば以下のようなものがあります。

交換法則とまとめpushのあわせ技

i + 2 よりも、2 + iのほうがまとめpushしやすくなります。
i = i + 2;の例です。
最適化前(21 bytes):
                96 03 00 00 69 00 96 03 00 00 69 00
                1C 96 03 00 00 32 00 0A 1D
まとめpush適用(18 bytes):
                96 06 00 00 69 00 00 69 00 1C 96 03
                00 00 32 00 0A 1D
2+iとしてまとめpush(15 bytes):
                96 09 00 00 69 00 00 32 00 00 69 00
                1C 0A 1D

Increment/Decrementを使う

±3までなら、足し算・引き算は、+1、-1を行う命令を使うほうがお得です。 上のi = i + 2の例で、Increment命令(0x50)を使うと以下のように12 bytesになります。
96 06 00 00 69 00 00 69 00 1C 50 50 1D

Duplicateを使う

同じ文字がたくさん並んだ文字列を作りたいなら、スタックトップを複製するDuplicate命令(0x4C)が使える場合があります。 Aが123回連続する文字列をxに代入したいなら、このように書けます。
96 09 00 00 78 00 00 41 41 41 41 00
4C 21 4C 21 4C 21 4C 21 4C 21 96 08
00 00 31 00 00 31 32 33 00 15 1D
赤い部分でAが128文字続く文字列を作ってます。やっていることを、アクションスクリプトで無理やり書くとこんな感じです。
tmp4 = "AAAA";
tmp8 = tmp4 add tmp4;
tmp16 = tmp8 add tmp8;
tmp32 = tmp16 add tmp16;
tmp64 = tmp32 add tmp32;
x = substring(tmp64 add tmp64, 1, 123);

まとめ

スタックに値を積む処理は、値の長さが小さいとヘッダの部分が3バイトあるのが気になります。Increment, DecrementやDuplicateをうまく組み合わせて使うことで、スタックに値を積む回数を減らすことができるので、これがコードサイズの節約につながります。

2007年06月03日

FlashLite: コードサイズが小さいActionScript

FlashLiteのアプリを携帯端末で動かす場合、サイズの制約を満たすためにコードサイズを節約することが必要になる場合があります。コードサイズを節約するために、アクションスクリプトではどんなことができるのでしょう。短い変数名を使うなどはもちろん効果がありますが、例えばこんなこともできます。

ordを使う

数値を代入する代わりに、ord(文字列)を使うことができる場合があります。たとえば、
i = 100;
は、
i = mbord("d");
と書いてもiに代入される値は同じになります。(dのASCIIコードは100です。)
バイトコードで見てみると、
前者: 96 03 00 00 69 00 96 05 00 00 31 30 30 00 1d
後者: 96 03 00 00 69 00 96 03 00 00 64 00 36 1d
と、後者の方がサイズが小さくなります。mbordでなくordでも原理的には同じことができるはずですが、Flash Professional 8ではコンパイル時にord("d")を展開して100にしてくれるようで、前者とおなじコードが生成されました。 3桁以上の場合に得をして、SJISの文字コードの関係で使えない範囲があるので、100-128,160-223,253-255の96種類の数値に対して有効です。あとかなり使える場合は少ないと思いますが、同様にしてSJISの漢字1文字を5ケタの整数と対応させることもできます。この場合は2バイト節約可能です。

最初の文字を取り出す

文字列から一部分を取り出すにはsubstring命令を使いますが、最初の1文字だけを取り出すのならよりサイズを節約できる方法があります。
x = substring(s,1,1);
は、
x = chr(ord(s));
と書くことができます。バイトコードにすると、この場合は11バイト短くなります。このようなことができるのは、ordは最初の1文字だけを対象にするからです。
前者: 96 03 00 00 78 00 96 03 00 00 73 00 1c
     96 03 00 00 31 00 96 03 00 00 31 00 15 1d
後者: 96 03 00 00 78 00 96 03 00 00 73 00 1c
     32 33 1d
pushのまとめ効果を考慮に入れても8バイト短くなります。
前者: 96 06 00 00 78 00 00 73 00 1c
     96 06 00 00 31 00 00 31 00 15 1d
後者: 96 06 00 00 78 00 00 73 00 1c
     32 33 1d

実はたくさん使える短い名前の変数

マニュアルによれば、「変数名には、英数字とドル記号 ($) のみを使用できます。変数名の先頭に数字は指定できません。」とあります。しかもFlashLiteの場合変数名の大文字小文字は区別されませんから、1文字の変数はa-zと$の27種類しか使えないということになります。 しかしこれはアクションスクリプトの文法上の制限で、実はもっとたくさんの1文字変数が使えます。 ちょっと復習になりますが、以下の2つの文は同じx1という変数に値を代入しています。これは配列エミュレートのために使われますね。
x1 = 12345;
eval("x" add 1) = 12345;
数字を連結することは必ずしも必要なく、
x = 12345;
eval("x") = 12345;
上の2文は全く等価なものです。
eval("x") = 12345;
trace(eval("x"));
とすれば当然12345が出力されます。
では、以下を実行したら何が起きるでしょう。
eval("1") = 12345;
trace(eval("1"));



実はこれを実行しても12345が出力されます。1という変数に値が代入されたのです。当然1 = 12345;という書き方はできませんが、evalを使うことで空白文字でも"\n"でも変数名に使うことができます。1バイトの文字列として使える文字の数は195個ありますが、その中で大文字小文字の区別を考慮して、/, ., :は特別な意味に扱われそう(未調査)なので除外すると、全部で166個の1文字変数を使えることになります。

未定義の変数の活用

未定義の変数を参照すると空文字列が返されます。これは、数値としては0のように扱われます。 たとえば下の100から109までを出力するプログラムでは、
for(i=0;i<10;++i){
  trace(100 + i);
}
iがそれまでに使われないのが明らかであれば最初のi=0の初期化は不要ということになります。

応用編

特殊な1文字変数と未定義の変数値についての知識を応用すると、以下のようなことが書けます。
普通のスクリプト(47バイト)
b = (x==y)?"Hello":"";

同じことをする別な書き方(40バイト)
eval(1) = "Hello";
b = eval(x==y);
ただし、変数"0"は未定義であるとします。x==yは0または1になります。それをevalすることで変数"0"または変数"1"の値をbに代入しています。

注意点

FlashLiteでコードサイズを小さくするための工夫をいくつか紹介しましたが、これらを適用しても、条件によっては必ずしもコードサイズが減るわけではありません。
また、ほぼ間違いなくコードが読みにくくなってしまうと思います。

2007年05月30日

FlashLite: SWF で FizzBuzz ってみた

同僚の竹迫さんがFlashLiteでFizzBuzz Golf(いかに短いサイズでFizzBuzzを書くか)をしていたので、私もチャレンジしてみました。

FlashLite1.1で195 byte になりました。FlashLite1.1で200バイトを切るのは大変でしたが、ちょっとした面白いことを知りました。

たとえば、
X = 100; よりも X = ord("d"); の方が1バイトお得
とか、
最初の文字をとりだす時には、substring(s,1,1); よりも chr(ord(s)); の方がお得
とか。
これらはアクションスクリプトだけを見ているとよくわかりませんが、バイトコードを眺めていると気がつくものです。他にもいくつかあるのでこれから少しずつ紹介していこうと思います。

2007年04月20日

Perl: オブジェクトのunbless

perlのblessはオブジェクトにクラス名をマッピングする命令です。

my $a = bless {}, "AA::BB::CC";
print ref($a), "\n";

を実行すると、AA::BB::CCが表示されます。

ほかの名前でblessし直すとその名前で古い対応関係が上書きされます。ではその対応関係を解除するにはどうすればよいのでしょう。bless $a, "HASH"などとすれば、ref関数の結果はblessする前と同じになりますが、依然としてblessされたままです。たとえば、Scalar::Utilのblessed命令を実行すると、blessされたままであることがわかります。

blessの解除を行うために、Data::Structure::Utilパッケージのunblessという命令が使えます。

use Scalar::Util qw(blessed);
use Data::Structure::Util qw(unbless);
my $a = bless {}, "TEST";
unbless($a);
print "Hello\n" if blessed($a);

これを実行しても、Helloは表示されません。同じ用途で、Acme::Damnのdamnも使えます。

use Scalar::Util qw(blessed);
use Acme::Damn qw(damn);
my $b = bless {}, "TEST";
damn($b);
print "Hello\n" if blessed($b);

これも、同じ結果になります。どちらも、ソースを見てみるとperlだけではこれは記述できなくて、xsのなかで、

SvOBJECT_off(sv);

を実行しています。blessされたオブジェクトであることを表しているフラグを0にしているわけです。一旦blessしたオブジェクトに対して、そのblessを解除するのって、実はそんなに簡単なことではないんですね。

2007年03月26日

FlashLite: 定数を変数に入れる基準

Flashでは変数名や定数など、頻繁に使用する文字列はLookup Tableに入れてコードサイズを節約してくれますが、FlashLite1.xにはその機能がありません。ですので、頻繁に使う定数などは短い名前の変数に入れてしまう、ということを自分でやることでコードサイズの節約ができる場合があります。FL1では、数値のデータも文字列としてコンパイルされるため、65536とかを大量に使う人には役に立つかもしれません。

で、このエントリーでは、何回使ったらお得なのかというのを計算してみようと思います。

65536を参照するときは、SWFでは65536をスタックにプッシュするという処理にコンパイルされます。

push [65536]

16進ダンプすると、以下の10バイトになります。
96 07 00 00 36 35 35 33 36 00

65536の5文字 + 5バイトというわけです。

一方変数Aの値の参照は、以下の7バイトになります。

push [A]
getvar

96 03 00 00 41 00 1C

また、A=65536の代入処理は、以下の17バイトになります。

push [A]
push [65536]
setvar

96 03 00 00 41 00 96 07 00 00 36 35 35 33 36 00 1D

これは、定数長5文字+12バイトです。

したがって、たとえば65536を10回使うとすると、定数のまま使うと10 x 10バイトで100バイト、変数Aに65536を代入してから使うと17バイト+10 x 7バイトで87バイトで、変数Aに代入するほうが13バイト少ないということになります。

もし、A=65536の代入を行うときに、Aと65536をまとめてpushしてよいことにすると、さらに3バイト節約になります。

一般に長さnの定数をk回使用するとき、1文字の変数に代入してから使うほうがバイトコードのサイズが小さくなる条件は、
12+n - (n + 5 - 7) x k < 0
である、ということになります。
これを整数kについて解くと、
minimum(k) = CEILING((13+n)/(n-2))
となります。ただし、CEILING(x)はxを下回らない最小の整数です。
(まとめプッシュありの場合はminimum(k) = CEILING((10+n)/(n-2)))

実際の値を入れて表にしてみました。65536ならn=5なので、6回以上使うならお得ということになります。

nk(まとめpushなし)k(まとめpushあり)
31613
497
565
654
744
843
943
10 33
11 33
12 33
13 33
14 32
15 32
16 32
17 22

また、剰余(%)演算に定数が含まれる場合、たとえば

X = Y % 65536;
は、
X = Y - int(Y / 65536) * 65536;

に変換されてコンパイルされるので、定数が使用される回数は1回ではなく2回とカウントします。

剰余演算をたくさん使っているときなどで、予想以上にコードサイズが大きくなってしまった場合など、定数を変数に代入することによってコードサイズの削減効果が期待できそうです。
とはいっても、せいぜい数10バイト程度かもしれませんが。