« 2007年05月 | メイン | 2007年07月 »

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