Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                
SlideShare a Scribd company logo
条件分岐とcmovとmaxps


     Cybozu Labs
   2011/8/6 光成滋生
  x86/x64最適化勉強会#1
内容
自己紹介
最適化の心得
プロファイル
ベンチマーク環境
条件分岐は重たい
条件分岐を減らす古典的なテクニック
cmovを使う
条件分岐は重たい?
SIMD命令を使う
maxps, pmaxsd
                     2 /24
自己紹介
光成滋生(サイボウズ・ラボ)
姑息な最適化が大好き
以前はコーデック系の趣味・仕事をやっていた
最近は仕事のために機械学習を勉強中
 PRMLあんちょこ公開中(https://github.com/herumi/prml/)
blog : http://homepage1.nifty.com/herumi/
mail : herumi@nifty.com
twitter : @herumi




                                                  3 /24
最適化の心得
プログラム最適化の第一法則
最適化するな
プログラム最適化の第二法則
 まだするな


そんなことはわかってる
      人たちが集まってるよね…




                      4 /24
プロファイル
プロファイラ
関数組み込みタイプ
 プログラム自体に組み込まれる
 それ自体がプログラムに影響を与える
 回数は正確
 gprofやDevPartner, Vtune
タイミング割り込みによる集計タイプ
 割り込みでその瞬間のeipを取得し集計
 プログラムに影響を(殆ど)与えない
 回数は不正確,時間は概ね正確
 CodeAnalyst, perf, VTune
推測するな,実測せよ(もちろん仮説と検証も大事)
                             5 /24
rdtsc
nsecレベルの時間取得が可能
時刻取得システムコールはそれほど分解能が高くない
 HPET(High Precision Event Timer)が使えるなら多少はましだが
最近のCPUの周波数が可変だと多少困る
マルチコアだと多少困る
 プロセッサを判別できるrdtscpというのもある
シリアライズされてない,それ自体が若干時間を食う
が,お手軽なのがよい(以下はXbyakでの例)
         Xbyak::util::Clock clk;
         clk.begin();
         計測したい部分
         clk.end();
         printf("%d¥n", clk.getClock());
                                             6 /24
ベンチマーク環境
今回のコード
https://github.com/herumi/opti/
要Xbyak
 http://homepage1.nifty.com/herumi/soft/xbyak.html
テスト環境
Xeon X5650 2.67GHz + Linux 2.6.32 + gcc 4.6.0
Core i7-2600 CPU 3.40GHz + Linux 2.6.35 + gcc 4.4.5
 ただしWindows 7上のVMware上
Core i7-2600 Cpu 3.40GHz + Windows7(64bit) + VC2010



                                                       7 /24
古典的なテクニック
範囲チェック
 unsigned int a, b, x;
 if (a <= x && x <= b) { ... }


分岐が2回
 cmp x, a
 jb #elese
 cmp x, b
 ja #else
 ...

次のようにすると分岐は1回
 if ((x – a) <= (b – a)) { ... }

後半の文字列で再度登場
                                   8 /24
条件分岐は重たい
不要な分岐を除去せよ
Intel64 and IA-32 Architectures Optimization Reference
 Manual コーディング規則1
CPUのパイプラインを乱す
 大抵10段以上ある
スループットがあがらない




                                                     9 /24
例題1. countMax
次の関数を最適化せよ
 分岐命令をcmovやsetgに置き換えよ
   (注意)x[i], y[i] >= 0を仮定する

size_t countMax_C(const int *x, const int *y, size_t n)
{
    size_t count = 0;
    for (size_t i = 0; i < n; i++) {
        if (x[i] > y[i]) count++;
    }
    return count;
}




                                                          10 /24
jmp命令を使う
jmp-cmov.cpp抜粋
  L(".lp");
      mov(t, ptr [x + n * 4]); // x[i]を読んで
      cmp(t, ptr [y + n * 4]); // y[i]と比較して
      jle(".skip");            // x[i] <= y[i]ならskip
      add(a, 1);               // count++
  L(".skip");
      add(n, 1);
      jne(".lp");


1ループあたり10~12clk
重たい



                                                       11 /24
setg命令を使う
setXXは条件が満たされたときに8bitレジスタを1に
 設定する(外れたら0)命令
8bitなのが結構使いにくい
jmp-cmov.cpp抜粋
  L(".lp");
      mov(edx, ptr [x   + n * 4]); // x[i]を読んで
      cmp(edx, ptr [y   + n * 4]); // y[i]と比較して
      setg(dl);         // dl = x[i] > y[i] ? 1 : 0
      movzx(edx, dl);   // 上位24bitを0クリア
      add(eax, edx);    // countに加算
      add(n, 1);
      jne(".lp");


1ループあたり2~2.5clk!
                                                      12 /24
adc命令を使う
  L(".lp");
      mov(edx, ptr [y + n * 4]); // y[i]を読んで
      cmp(edx, ptr [x + n * 4]); // x[i]と比較して
      adc(eax, 0); // ???
      add(n, 1);
      jne(".lp");

adcはcarryつきadd命令
cmpしたときにcarry = y[i] < x[i] ? 1 : 0
eax = eax + 0 + carry
     = eax + (x[i] > y[i] ? 1 : 0);
(注意)符号無しの計算になるのでx[i], y[i] >= 0を仮定


                                                13 /24
条件分岐は重たい?
計ってみると1ループあたり1clk程度?
  L("@@");
      sub(ecx, 1);
      jnz("@b");

実は条件分岐の予測がはずれたとき重たい
最近のCPUの条件分岐予測機構は優秀なので結
 構当たる
あたった場合のコストはかなり低い




                        14 /24
条件分岐は重たい?
与えられた配列の最大値の取得(cmov vs. jge)
   int getMax_C(const int *x, size_t n)
   {
       int max = x[0];
       for (size_t i = 1; i < n; i++) {
           if (x[i] > max) max = x[i];
       }
       return max;
   }


// a = max                         // a = max
cmp(a, ptr [x + n * 4]);           cmp(a, ptr [x + n * 4]);
                                   jge(".skip");
// if (a > x[i]) a = x[i]          mov(a, ptr [x + n * 4]);
cmovl(a, ptr [x + n * 4]);       L(".skip");
                                                              15 /24
ベンチマーク
ランダムな値を入れた配列に対して
 cmovが2.7clkなのに対してjmpは1.8clk!
jmpの方が速い
ランダムな配列の最大値はどこか途中で出てくると
 それ以降は必ずそれより小さい
 分岐予測がヒットする
最適化マニュアルを見直す(コーディング規則2)
setcc, cmovを使って分岐を排除せよ.
 ただし,予測可能な分岐に対しては排除してはいけない
さて,配列の最大値で最後まで予測できないのは不規則
 に単調増加しているとき.そんなケースは多くなさそう
                                16 /24
もう少しベンチマーク
 大抵はjmpが速そうだ(@i7)
           ランダム    最初が一番大きい             単調増加       時々増加
  jmp      1.859       1.823            1.836      3.637
  cmov     2.741       2.718            2.747      2.751


 ちなみに最初のcountMaxのベンチマーク
  x[i] > y[i]となる割合
  一番予測できないときjmpの数値が非常に悪い
    rate    0.00    0.25        0.50        0.75    1.00

    jmp    1.863    7.827      12.747      9.276    1.854

    setg   2.054    2.043      2.052       2.026    2.054

    adc    1.842    1.838      1.849       1.828    1.822

                                                            17 /24
分岐点はどこか
概ね経験則で分岐予測がヒットしたとき<1clk
 外れたとき20clk
先程もrate=50%のときが12clk程度
cmovなどの条件分岐排除命令は数clk
ざっくり,分岐予測が1/10しか外れないなら互角かも
データがどういう性質を持っているかをよく見極め
 ることが大切




                              18 /24
SIMDを使ってみる
maxps = max(x[i], y[i]) for i = 0..3 を求める
   // xm0 = [x[3]:x[2]:x[1]:x[0]
   L("@@");
       maxps(xm0, ptr [x + n * 4]); // update xm0
       add(n, 4);
       jnz("@b");
    // ループが終わったらxm0のうちの一番大きいものを求める

     i7     ランダム     最初が一番大きい      単調増加      時々増加

     jmp     1.859      1.823       1.836      3.637

    cmov     2.741      2.718       2.747      2.751

    maxps    0.686      0.689       0.677      0.687



SIMD凄い!
                                                       19 /24
実はちょっとした落とし穴
Xeon, Core2Duoなどのプロセッサでは劇遅
Xeonだと30clk以上!
PentiumDだと速かったりする
maxpsは浮動小数点数(float)に対する命令
普通の整数値はfloatとしてみると非正規化数になっちゃ
 うことも多い
ペナルティを食らう
 float x[N]に対する先程の命令ではXeonだと0.648clk
PentiumDの頃は型にうるさく無かった?
整数に対するmax命令はpmaxsd
SSE4.1にしか無いがmaxpsよりは速い
                                        20 /24
ベンチマーク
 http://agner.org/optimize/blog/read.php?i=142
   i7は非正規数の扱いが高速になった
      i7      ランダム     最初が一番大きい     単調増加      時々増加
      jmp      1.859      1.823      1.836        3.637
     cmov      2.741      2.718      2.747        2.751
     maxps     0.686      0.689      0.677        0.687
    pmaxsd     0.276      0.279      0.287        0.273

     Xeon     ランダム     最初が一番大きい     単調増加      時々増加
      jmp      3.414      1.745      1.746        3.481
     cmov      5.008      2.613      2.612        2.653
     maxps    30.741     26.581      26.587       26.547
    pmaxsd     0.437      0.437      0.437        0.437

                                                           21 /24
maxssよりjmpがよいレアケース
floatに対する高速なexp, logの実装fmath.hpp
https://github.com/herumi/fmath/
gccの数倍から一桁以上速い
       Xeon     exp      expx4    log    logx4
      gcc 4.6   565.9    2265.7   54.1   222.7
       fmath    15.2      35.4    12.9   32.3


exp(x)はxがある値A以上で∞, -A以下で0とみなす
 その範囲でクリッピングしたい
                 minss(x, A);
                 maxss(x, -A);
                 ...


                                                 22 /24
maxssよりjmpがよいレアケース
maxss/minssのレイテンシーは3clkぐらい
依存関係が高いので隠蔽しにくい
通常expが0になったり∞になったりする値は扱わない
 範囲外はレアケースとして追い出す
 minps(x, A);    movd(edx, x);
 maxps(x, -A);   and(edx, 0x7fffffff);
 ...             ... // メインの作業
                 cmp(edx, A); // 整数として比較して
                 jg(".overflow");

                 L(".overflow"); // レアケースの場合は
                 minss(x, A);    // やり直し
                 maxss(x, -A);
                 jmp(".retry");
これで15.9clk → 14.2clkと少し速くなった
                                                23 /24
まとめ
条件分岐を排除せよ
ただし分岐予測ができないときのみ
意外とCPUの分岐予測に任せた方がよいこともある
cmov, setgは起こらなかったときのコストを薄く払う
可能ならSIMD命令を使う
トリッキーな非SIMDよりも簡単に速くできる(かも)
整数演算と浮動小数演算の命令に注意する
 同じ内容でも異なる命令が割り当てられている
   andpsとpand, movdqaとmovapsなど
SIMDよりもjmpがよいときもあることはある
いろんなパターンを考えよう
                                  24 /24

More Related Content

条件分岐とcmovとmaxps