三項演算子が遅い?

as3 version
ここ数日、検出器を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文になっただけです。これを計測すると以下のようになります

123avgratio
三項演算子2437(ms)245324372442.3333331
if文1875189118751880.3333330.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回まわすと以下のような結果になります×

123avgratio
三項演算子4453(ms)4375446844321
if文3109309331573119.6666670.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)を使いました×