Adobe AlchemyはFlashの隠し命令を使っているみたい
今日から、Adobe MAXですが、明日、川崎さん、id:amachang、id:gyuqueさん、id:moriyoshiさんとともに、C-6「Flashはもういらない」でしゃべってきます。お時間がございましたら、ぜひ、お越しください。僕は「Flash Playerの作り方」というタイトルで話します。皆様のお話、相当面白そうですよ!
昨日、MAX前夜祭がありました。Adobe の方の、プレゼンテーションを聞いていて、Flash 上でC言語のプログラムを走らせる、Adobe Alchemy の話を聞いていて、名前は出た当初から知っていたのですが、一度も実行したことがなかったので、実行させてみました。
ちなみに、Alchemy 0.5 は cygwin では、バグっていてコンパイルができなく、http://www.adobe.com/cfusion/webforums/forum/messageview.cfm?forumid=72&catid=755&threadid=1406576&enterthread=y にしたがって、Perl コードを書き換えないと、コンパイルできません。やられる方、お気をつけください。
そして、不思議なことに、Alchemy の生成した SWF は同じ Flash Player で動くのに、実行速度が ActionScript 3 で書くよりも速いです。ベクトルの内積のベンチマークで 1.6倍速かったです。理由がわからなかったので、Alchemy が生成する ABC (ActionScript Byte Code) を見てみました。
ABCをみるには、Tamarin の abcdump がいいのですが、これは、Flash 9 までしか対応していないので、Flash 10 に対応するべく、改造しました。abcdump のコンパイル方法は、http://www.5etdemi.com/blog/archives/2007/01/as3-decompiler/ と Tamarin の abcdump.as から abcdump.exe を作成する方法 2009年1月版 - てっく煮ブログ 跡地 で説明されているのですが、これらは、情報不足で、このとおりやってもうまくいかなく、Tamarin Build Documentation | MDN に従ってコンパイルする必要があります。(この説明も説明不足です。すいません。)
その上で作ったのが、http://yukoba.jp/blog/2009/abcdump_flash10.zip です。
Flash 10 対応の abcdump を実行してみて驚いたんですが、Flash 10 になってから、AVM2 の命令が増えています。Adobe から増えたという発表を僕は見たことがないですし、AVM2 Overview には記載されていません。隠し命令状態になっています。
先ほどのベクトルの内積を Alchemy で SWF 化したあと、abcdump で逆コンパイルすると、
for (i = 0 ; i < 100 * 1024; i++) { sum += ary1[i] * ary2[i]; }
の部分は、
L20: 1018 label L19: 1019 getlocal0 1020 getlocal0 1021 getproperty i3 1024 getlocal0 1025 getproperty i6 1028 add 1029 initproperty i7 1032 getlocal0 1033 getlocal0 1034 getproperty i4 1037 getlocal0 1038 getproperty i6 1041 add 1042 initproperty i8 1045 getlocal0 1046 getlocal0 1047 getproperty i8 1050 OP_0x39 1051 initproperty f1 1054 getlocal0 1055 getlocal0 1056 getproperty i7 1059 OP_0x39 1060 initproperty f2 1063 getlocal0 1064 getlocal0 1065 getproperty f1 1068 getlocal0 1069 getproperty f2 1072 multiply 1073 initproperty f1 1076 getlocal0 1077 getlocal0 1078 getproperty i6 1081 pushbyte 8 1083 add 1084 initproperty i6 1087 getlocal0 1088 getlocal0 1089 getproperty i5 1092 pushbyte 1 1094 add 1095 initproperty i5 1098 getlocal0 1099 getlocal0 1100 getproperty f1 1103 getlocal0 1104 getproperty f0 1107 add 1108 initproperty f0 1111 getlocal0 1112 getproperty i5 1115 pushint 102400 // 0x19000 1117 equals 1118 iftrue L21 1122 jump L20 L21:
になります。読みにくいので、ソースに近い擬似コードの形に変換すると、
L20: i7 = i3 + i6 i8 = i4 + i6 f1 = OP_0x39 と i8 // *((double*)i8) f2 = OP_0x39 と i7 // *((double*)i7) f1 = f1 * f2 i6 = i6 + 8 i5 = i5 + 1 f0 = f0 + f1 if (i5 == 102400) goto L21 goto L20 L21:
ループ変数の i が i5 になっていて、i3, i4 が 配列のポインタです。f0 が sum です。i6 = i * 8 です。
問題は、OP_0x39 の部分です。これが、アンドキュメントです。i8 で指定された番地のメモリを、double に型キャストして 読み出しているのだと思います。OP_0x33 〜 OP_0x3F はアンドキュメントで、このあたりは、指定された番地の直接読み書きを行っているのではないかと推測しています。Number を経由することなく、double を扱っていますし、そして、さらにメモリを直接読み書きする命令があるゆえ、Alchemy は高速化できているのだと思います。
もうひとつ面白いのは、AVM2 にはローカル変数があるのですが、上記の擬似コードでの変数は全て、メンバ変数です。スタックからの読み書きも、メンバ変数の読み書きも処理速度は変わらないという理由なのでしょうか?
そして、ここまでプリミティブに近い形で操作できているにもかかわらず、Visual C++ や Java に比べて4倍近く遅いです。なぜ遅いのか、これまた疑問です。最適化の過程で、適切にレジスタに割り振ることが必要なんですが、メンバ変数の読み書きをレジスタに割り振っているのでしょうか?よくわかりません。
以上、今現在でわかったのは、ここまでです。
(追記)
id:nitoyonさんの、
を発見。
(追記2)
Flash 10から13個命令が追加になったようです。詳細はコメント欄をご覧ください。
http://llvm.org/devmtg/2008-08/Petersen_FlashCCompiler.pdf
(追記3)
でも、実は、Adobe Alchemyはそれほど速くない可能性も。続きは、Flashで数値計算を高速化する方法 - yukobaのブログ。