三項演算子が遅い?
ここ数日、検出器をActionScript3(AS3)へ移植する作業をしていました。OpenCV(の検出器)をAS3で使いたい!という向きにはMarilenaというライブラリが既にあるのですが、単純に勉強になるということと、細かいところまで自分で把握していたほうがカスタマイズしやすいという点を考えて、敢えてイチから書いています×
さて、物体検出というのはなかなか重い処理でして、識別器の適用処理(OpenCVのオリジナルコードでいうと cvRunHaarClassifierCascade関数)は、画像のサイズにもよりますが何万とか何十万とかいうオーダーで呼び出されます。AS3に移植するときは、ここが重くならないように気をつけなければいけません×
AS3の最適化においては(AS1/2でもそうですが)、メソッド呼び出しのオーバーヘッドが馬鹿にならないので、メソッド呼び出しを(涙を飲みつつ)インラインのべた書きに直していくという作業が主になります。ところが、メソッド呼び出し以外に、意外なところで高速化のポイントが見つかりました×
オリジナルのOpenCVのコードで言うと次の部分です×
a = classifier->alpha[0]; b = classifier->alpha[1]; stage_sum += sum < t ? a : b;
個々の弱識別機の適用結果を判定して、Stage全体のスコアを操作する処理です。Marilenaで該当する箇所は
val += (sum < feature.threshold * variance_norm_factor) ? feature.left_val : feature.right_val;
ここです×
これをこう書くとどうでしょうか×
if (sum < feature.threshold * variance_norm_factor) val += feature.left_val; else val += feature.right_val;
三項演算子がif文になっただけです。これを計測すると以下のようになります
回 | 1 | 2 | 3 | avg | ratio |
---|---|---|---|---|---|
三項演算子 | 2437(ms) | 2453 | 2437 | 2442.333333 | 1 |
if文 | 1875 | 1891 | 1875 | 1880.333333 | 0.76989218 |
20%以上速くなりました。ふしぎ!ふしぎ! X > _ < X
と、ここまで読んで「三項演算子って遅いんだ!三項演算子ヤバイ!超ヤバイ!」とか言って手持ちのコードを書き換えに行くのはちょっと待ってください。次のようなコードの場合は速度にはほとんど差がありません。
public static function cmp1(a:int, b:int):int { return (a<b) ? 2 : -2; } public static function cmp2(a:int, b:int):int { if (a<b) {return 2;} else {return -2;} }
上の例では、cmp1とcmp2で若干違うバイトコードが生成されますが、速度に有意差はありません。三項演算子が問題を起こすのは次のようなコードです
public static function cmp1(a:int, b:int):int { var c:int = 0; c += (a < b) ? 2 : -2; return c; } public static function cmp2(a:int, b:int):int { var c:int = 0; if (a < b) { c += 2; } else { c -= 2; } return c; }
三項演算子の結果を使って変数cの値を操作しています。OpenCVの問題の個所に近い処理です。これを20000000回まわすと以下のような結果になります×
回 | 1 | 2 | 3 | avg | ratio |
---|---|---|---|---|---|
三項演算子 | 4453(ms) | 4375 | 4468 | 4432 | 1 |
if文 | 3109 | 3093 | 3157 | 3119.666667 | 0.703895909 |
やはり三項演算子が格段に遅いです。生成されたABCを比較してみます×
↓三項演算子
0 getlocal0 1 pushscope 2 pushbyte 0 4 setlocal3 5 getlocal3 6 getlocal1 7 getlocal2 8 ifnlt L1 12 pushbyte 2 14 coerce_a 15 jump L2 L1: 19 pushbyte -2 21 coerce_a L2: 22 add 23 convert_i 24 setlocal3 25 getlocal3 26 returnvalue
↓if文
0 getlocal0 1 pushscope 2 pushbyte 0 4 setlocal3 5 getlocal1 6 getlocal2 7 ifnlt L1 11 getlocal3 12 pushbyte 2 14 add 15 convert_i 16 setlocal3 17 jump L2 L1: 21 getlocal3 22 pushbyte 2 24 subtract 25 convert_i 26 setlocal3 L2: 27 getlocal3 28 returnvalue
三項演算子で書いた場合のみ、coerce_aが生成されています。この命令は、avm2の仕様書によると Any型( * )に値を変換する命令だそうです。これは重くなるわけです。尚、ここでは整数演算しかしてませんが、実数演算の場合も同様に三項演算子の方が遅いという結果が出ます×
というわけで、三項演算子を使わないほうがいい場面もあるという話でした。ABCアセンブリの生成には、id:nitoyonさんのabc抽出プログラム(d:id:nitoyon:20080401)とabcdump(http://iteratif.free.fr/blog/index.php?2006/11/15/61-un-premier-decompileur-as3)を使いました×