SlideShare a Scribd company logo
1 of 24
Download to read offline
フラグを愛でる


2013/3/30 光成滋生(@herumi)
x86/x64最適化勉強会5(#x86opti)
目次
 さまざまなコード片でcarryの扱いを味わってみる
   フラグの復習
   絶対値の復習
   ビットの長さ
   ビットカウント
     cybozu/WaveletMatrixの紹介
   多倍長整数加算
   Haswellで追加された命令達

     題材があちこち飛びます m(__)m




2013/3/30 #x86opti 5            2 /24
フラグの復習
 演算ごとに変化する1bitの情報群
 よく使うもの
   ZF : Zero flag : 演算結果が0ならtrue
   CF : Carry flag : 演算に桁上がり、桁借りがあればtrue
   SF : Sign flag : 演算結果が負ならtrue
例           mov eax, 5
             neg eax      ; -5になるのでSF = 1, ZF = 0

             mov eax, 0x80000001
             mov ecx, 0x80000002
             add eax, ecx ; 32bitを超えるのでCF = 1
 条件つきmov命令
   フラグの条件が成立すれば代入
     cmovz eax, ecx ; ZF = 1ならeax ← ecx
2013/3/30 #x86opti 5                                3 /24
絶対値(1/3)
 int abs(int x) { return x >= 0 ? x : -x; }
   -x = ~x + 1
   ~x = x ^ (-1)
   組み合わせると –x = x ^ (-1) – (-1)
   またx ^ 0 – 0 = x
   xが0以上の時も、xが負のときもx ^ M – Mの形をしている
   Mを作れればabsを分岐なしで実行できる
     古典的なトリック

       int abs(int x) {
           uint32_t M = (x >> 31); // M = x >= 0 ? 0 : (-1);
           return x ^ M – M;
       }



2013/3/30 #x86opti 5                                           4 /24
絶対値(2/3)
 gcc 4.7での実装
       // input : ecx, output : eax
       // destroy : edx, eax

       mov   edx,      ecx   ;   edx   = x
       sar   edx,      31    ;   edx   = (x < 0) ? -1 : 0;
       mov   eax,      edx   ;   eax   = M
       xor   eax,      ecx   ;   eax   ^= x ; eax = x ^ M
       sub   eax,      edx   ;   eax   -= M ; eax = x ^ M - M
 clang 3.3での実装
       mov      eax, ecx     ;   eax   =   x
       mov      edx, ecx     ;   edx   =   x
       neg      eax          ;   eax   =   -x
       cmovl    eax, edx     ;   eax   =   (x > 0) ? x : -x
   わかりやすい
   sandyだと少し速い(古いCPUだとgccのがよいことも)
2013/3/30 #x86opti 5                                            5 /24
絶対値(3/3)
 VCでの実装
       mov eax, ecx
       cdq             ; M = edx = (eax < 0) ? -1 : 0
       xor eax, edx    ; eax = x^ M
       sub eax, edx    ; eax = x^ M - M

   cmovより速い(ことが多い)
     cmov命令はIntel CPUではものすごく速いというわけではない
     core2duo, sandy bridgeと段々よくなったが
   レジスタ固定だが
         まあいまどきmovはほぼコスト0だし
   64bitだとcqo(0x48 0x99)
     VCのcod出力はcdqと表示されるので注意



2013/3/30 #x86opti 5                                    6 /24
ビットの長さ(1/4)
 xを確保するのに必要なビットの長さ
   x == 0のとき1とする
       int bitLen(uint32_t x) {
           if (x == 0) return 1;
           for (int i = 0; i < 32; i++) {
               if (x < (1u << i)) return i;
           }
           return 32;
       }

 __builtin_clzを使う
   これはcount leading zeroなので32から結果を引く
   この関数はx == 0のときは未定義なので別に処理する
       if (x == 0) return 1;
       return 32 - __builtin_clz(x);

2013/3/30 #x86opti 5                          7 /24
ビットの長さ(2/4)
 gcc 4.7とclang 3.3
        // gcc                   // clang
           test edi, edi         mov eax, 1
           mov eax, 1            test edi, edi
           je .Z                 je .Z
           bsr edi, edi          bsr eax, edi
           mov al, 32            xor eax, -32
           xor edi, 31           add eax, 33
           sub eax, edi
       .Z: ret               .Z: ret

 clangの方がちょっと賢い感じだがなんか微妙
   bsr(x) == 32 - __builin_clz(x)なので回りくどい
     少し変えてみる
       if (x == 0) return 1;
       // return 32 - __builtin_clz(x);
       return (__builtin_clz(x) ^ 0x1f) + 1;
2013/3/30 #x86opti 5                             8 /24
ビットの長さ(3/4)
 なぜかgccだけよくなった
   xorとaddがキャンセルした
         //       gcc                   // clang
                  test edi, edi          mov eax, 1
                  mov eax, 1             test edi, edi
                  je .Zero               je .Zero
                  bsr edi, edi           bsr eax, edi
                  add eax, 1             xor eax, -32
                                         add eax, 33
       .Zero: ret                 .Zero: ret

 VCでは_BitScanReverse(&ret, x)を使う
   これはx == 0のときfalseを返す

       unsigned long ret;
       if (_BitScanReverse(&ret, x)) return ret + 1;
       return 1;

2013/3/30 #x86opti 5                                     9 /24
ビットの長さ(4/4)
 VCはbsrは入力が0ならZF=1なことを知っている
                bsr    eax, ecx
                je     .zero
                inc    eax
                ret
         .zero: mov    eax, 1
                ret

 いや, でもZF = 1のときはecx = 0なんだし
  こうすればすっきりする
          bsr eax, ecx
          cmovz eax, ecx
          inc eax
          ret

   ただしx == 0が殆どありえないなら上の方が速いかも
2013/3/30 #x86opti 5              10 /24
減算のあとのmod p(1/2)
 暗号ではX={0, 1, ..., p-1}の中の四則演算をよく使う
   x, y ∈ Xに対して(x + y) % pとか(x – y) % pとか
        // 引き算の疑似コード
        // const uint255_t p = ...;
        uint255_t sub(uint255_t x, uint255_t y) {
           if (x >= y) return x – y;
           return x + p – y;
        }

   大小比較って結局は引いてみないと分からない
       uint255_t add(uint255_t x, uint255_t y) {
          int256_t t = x – y;
          if (t < 0) t += p;
          return t; }
   分岐はランダムなので10clk以内なら条件jmpは避けたい

2013/3/30 #x86opti 5                                11 /24
減算のあとのmod p(2/2)
 sub + sbb後のCFを利用してaddすべき値をcmov
       // 疑似コード
       sub x0, y0
       sbb x1, y1
       sbb x2, y2
       sbb x3, y3 // [x3:x2:x1:x0] – [y3:y2:y1:y0]
       t0 = t1 = t2 = t3 = 0;
       cmovc [t3:t2:t1:t0], [p3:p2:p1:p0] ; t = (x < y) ? p : 0
       [x3:x2:x1:x] += [t3:t2:t1:t0]

   256bit減算なので64bitレジスタ x 4を使う
     実際にはcmovなどが4個並んでる
 ルール : 分岐予測不可→cmov→可能なら単純命令に
   0に設定してcmovよりマスクして&が少し速い(CPUによる)
       sbb m, m // m = (x < y) ? -1 : 0
       [t3:t2:t1:t0] = [p3:p2:p1:p0]
       [t3:t2:t1:t0] &= m
2013/3/30 #x86opti 5                                              12 /24
128bit popcnt(1/3)
 Wavelet行列の中で使う簡潔ベクトル構造の中
   結局, 今のところ使わなかったけど面白かったので紹介
     idxから128bit分のマスクを作って[b1:b0]と&をとってpopcnt
     idx&127>=64なら[m1:m0]=[*:-1]。<64なら[m1:m0]=[0:*]
              |    b0         |      b1       |
              |0|1|2|3|4|5|6|7|8|9|a|b|c|d|e|f|
             m|***************|****           | idx & 127 >= 64
              |**********     |               | idx & 127 < 64
              |    m0         |       m1      |

  uint64_t maskAndPopcnt(uint64_t b0, uint64_t b1, uint64_t idx){
    const uint64_t mask = (uint64_t(1) << (idx & 63)) - 1;
    uint64_t m0 = (idx & 64) ? -1 : mask;
    uint64_t m1 = (idx & 64) ? mask : 0;
    uint64_t ret = popcnt(b0 & m0);
    ret += popcnt(b1 & m1);
    return ret;
  }
2013/3/30 #x86opti 5                                                13 /24
128bit popcnt(2/3)
 gcc 4.7
   ジャンプ命令を使う…論外
 VC2012
   idx & 64 == 0の判定を2回する…おしい
 clang 3.3
   idx & 64 == 0の判定は1回だがなぜかシフト
     しかも作ったフラグをつぶす(clangあるある)
  // edx = idx         ZFを保存するため          最適解?
  and edx, 64          順序入れ換える           ecxとedxを入れ換えたら
  shr edx, 6                             xor不要
  xor ecx, ecx         xor ecx, ecx      or rcx, -1 ;3byte減る
  test edx, edx        and edx, 6        and edx, 6
  cmovneq rcx, rax     cmovneq rcx,rax   cmovneq rdx, rax
  mov     rdx, -1      mov rdx, -1
  cmoveq rdx, rax      cmoveq rdx, rax   cmoveq rcx, rax
2013/3/30 #x86opti 5                                       14 /24
128bit popcnt(3/3)
 ちょっと宣伝
   SucVectorとWaveletMatrixクラス開発中
     実装済みメソッド : get, rank, rankLt, select
     Yasuo.Tabeiさんのfmindex++に組み込んでみた
     実験コード
     https://github.com/herumi/fmindex
   200MBのUTF-8テキストから1400個の単語(平均12byte)
    の全出現位置列挙(locateの呼び出し24M回)
       実装                         時間[sec]        lookup[clk]     rank[clk]
       オリジナル(wat_array)                 160               1887         1887
       wavelet-matrix-cpp(wm)               72             883          598
       cybozu/WaveletMatrix(cy)             30             343          183

     wat_arrayは岡野原さん, wavelet-matrix-cppはmanabeさん作
     wmに比べてもcyはrankが約3.2倍, lookupが2.5倍
2013/3/30 #x86opti 5                                                          15 /24
多倍長整数の加算(1/5)
 前半発表のlliの出力(一部)
         .lp: mov r9,   qword [rsi]       ;   x[i]
              add       r9, qword [rdx]   ;   +y[i]
              setb      al                ;   al = carry ? 1 : 0
              movzx     r8d, r8b          ;   一つ目のcarry
              and       r8, 1
              add       r8, r9            ; x[i] + y[i] + carry
              mov       qword [rdi], r8   ; 保存
              add       rsi, 8
              add       rdx, 8
              add       rdi, 8
              dec       rcx               ; ループカウンタ
              mov       r8b, al           ; 今回のフラグを保存
              jne       .lp
 なんかえらいことに
   実はとても遅いというわけではなかったり(たまたま)

2013/3/30 #x86opti 5                                               16 /24
多倍長整数の加算(2/5)
 加算でやらしいところ
   carryつきaddを実行したいのでcarryを変更してはいけない
   でもループ変数はいじらないといけない
   コンパイラに任せると先程のフラグを保存するコードになる
 抜け道
   add, sub, adc, sbbはCFとZFを変更するが
    inc, decはCFを変更しない
   ループカウンタcを-nから0方向にインクリメント
     x, y, zのアドレスはあらかじめn * 8を足してずらしておく

       .lp: mov(t, ptr [x + c * 8]); // t = x[i]
            adc(t, ptr [y + c * 8]); // t = x[i] + y[i] + carry
            mov(ptr [z + c * 8], t); // z[i] = t
            inc(c);
            jnz(".lp");
2013/3/30 #x86opti 5                                              17 /24
多倍長整数の加算(3/5)
 Xeon X5650(Westmere)では
   ループあたり13clkもかかる
     なんと先程のLLVMの(酷い)コードよりも遅い!
   フラグに関するパーシャルレジスタストール
     Intelの最適化マニュアル
      「INCとDECはADDやSUBに置き換えるべきだ」
     置き換えたら動かないんですけど

      .lp: mov(t, ptr [x + rcx * 8]); // t = x[i]
           adc(t, ptr [y + rcx * 8]); // t = x[i] + y[i] + carry
           mov(ptr [z + rcx * 8], t); // z[i] = t
           sub(c, 1); // adcのキャリーを破壊する
           jnz(".lp");




2013/3/30 #x86opti 5                                               18 /24
多倍長整数の加算(4/5)
 jrcxz/jecxz命令
   rcx, ecxがゼロなら分岐する命令
     みなさん覚えてますか? 私は忘れてました
         Pentiumで遅かったのでloop命令とともに封印された(私の中で)
     jnrcxzは無いのでループで使うとねじれるのが難
   core2duo以降はそこそこ速い
     .lp: jrcxz(".exit");
          mov(t, ptr [x + c * 8]);
          adc(t, ptr [y + c * 8]);
          mov(ptr [out + c * 8], t);
          lea(c, ptr [c + 1]);
          jmp(".lp");

   16回ループ(1024bit加算)が208clk→62clk


2013/3/30 #x86opti 5                            19 /24
多倍長整数の加算(5/5)
 Sandy Bridgeでは改良された
   元のコード(adc + dec)の方が速い
   先頭だけ外に出す微調整でもっと速くなった(なぜ)

  1024bit(64x16)加算          adc + dec LLVM adc + jrcxz adc + dec(その2)
  Core2Duo(Win)             215         ---     55            221
  Xeon X5650(Westmere) 208              63      62            202
  sandy bridge              48          64      52            33

   https://github.com/herumi/opti/blob/master/uint_add.cpp

 多倍長の乗算                                      read+modify+writeが2clk/64bit
   同様にmulがフラグを変更するのが邪魔
   レジスタの使い回しで非常に苦労する
   速度低下にもつながる

2013/3/30 #x86opti 5                                                   20 /24
Haswellで追加された命令(1/3)
 無視されるフラグたち
                       命令 動作
                       adcx 符号なし加算(CFのみ変更)
                       adox 符号なし加算(OFのみ変更)
                       mulx 符号なし乗算(フラグ変更なし)
                       sarx 算術右シフト(フラグ変更なし)
                       shlx   論理左シフト(フラグ変更なし)
                       shrx   論理右シフト(フラグ変更なし)
                       rorx   論理右回転(フラグ変更なし)
 未定義だった部分が確定した命令
                       lzcnt bsrの拡張
                       tzcnt bsfの拡張
2013/3/30 #x86opti 5                            21 /24
Haswellで追加された命令(2/3)
 ビット操作系
   andn(x, y) = ~x & y
     今更感が
   bextr(x, start, len)
     xの[start+len-1:start]のビットを取り出す(範囲外は0拡張)
   blsi(x) = x & (-x)
     下からみて初めて1となってるビットのみを取り出す
   blsr(x) = x & (x-1)
     上からみて初めて1となってるビットのみを取り出す
   blsmsk(x) = x ^ (x-1)
     下からみて初めて1となるところまでのマスクを作る
   bzhi(x, n) = x & (~((-1) << n))
     nビットより上をクリア

2013/3/30 #x86opti 5                            22 /24
Haswellで追加された命令(3/3)
   pdep(x, mask)
     maskのビットがたっているところにxのビットを埋め込む
                   x   x5   x4   x3   x2   x1   x0

              mask     1    1    0    0    1    0



            result     x2   x1   0    0    x0   0

   pext(x, mask)
     maskのビットが立っているところのxのビットを取り出す
                   x   x5   x4   x3   x2   x1   x0

              mask     1    1    0    0    1    0



            result     .    .    .    x5   x4   x1
2013/3/30 #x86opti 5                                 23 /24
おまけ
 今どきのコンパイラはかしこい
 ハッシュ関数(fnv-1a)のループ内演算
   https://github.com/herumi/misc/blob/master/fnv-1a.cpp
       for (size_t i = 0; i < n; i++) {
         v ^= x[i];
         v += (v<<1)+(v<<4)+(v<<5)+(v<<7)+(v<<8)+(v<<40);
       }
   「v += シフト&加算」の部分はv *= p(41bitの素数)の形
     pのハミング重み(2進数展開の1の数)が小さいものを探して選ぶ
   gcc 4.7は素直にleaやaddやshlを組み合わせたコード生成
   clang, VCはmulに置き換えた! mov r10, 1099511628211 ; p
     こっちのほうが2.4倍速い@i7                   .lp:
                                           movzx   r8d, byte [r9+rcx]
     ハミング重みにこだわらない                        inc     r9
      関数探索もありか?                            xor     rax, r8
                                           imul    rax, r10
2013/3/30 #x86opti 5                                                    24 /24

More Related Content

What's hot

高速な倍精度指数関数expの実装
高速な倍精度指数関数expの実装高速な倍精度指数関数expの実装
高速な倍精度指数関数expの実装MITSUNARI Shigeo
 
暗号文のままで計算しよう - 準同型暗号入門 -
暗号文のままで計算しよう - 準同型暗号入門 -暗号文のままで計算しよう - 準同型暗号入門 -
暗号文のままで計算しよう - 準同型暗号入門 -MITSUNARI Shigeo
 
Pythonの理解を試みる 〜バイトコードインタプリタを作成する〜
Pythonの理解を試みる 〜バイトコードインタプリタを作成する〜Pythonの理解を試みる 〜バイトコードインタプリタを作成する〜
Pythonの理解を試みる 〜バイトコードインタプリタを作成する〜Preferred Networks
 
冬のLock free祭り safe
冬のLock free祭り safe冬のLock free祭り safe
冬のLock free祭り safeKumazaki Hiroki
 
目grep入門 +解説
目grep入門 +解説目grep入門 +解説
目grep入門 +解説murachue
 
AVX2時代の正規表現マッチング 〜半群でぐんぐん!〜
AVX2時代の正規表現マッチング 〜半群でぐんぐん!〜AVX2時代の正規表現マッチング 〜半群でぐんぐん!〜
AVX2時代の正規表現マッチング 〜半群でぐんぐん!〜Ryoma Sin'ya
 
x86とコンテキストスイッチ
x86とコンテキストスイッチx86とコンテキストスイッチ
x86とコンテキストスイッチMasami Ichikawa
 
暗号化したまま計算できる暗号技術とOSS開発による広がり
暗号化したまま計算できる暗号技術とOSS開発による広がり暗号化したまま計算できる暗号技術とOSS開発による広がり
暗号化したまま計算できる暗号技術とOSS開発による広がりMITSUNARI Shigeo
 
ARM CPUにおけるSIMDを用いた高速計算入門
ARM CPUにおけるSIMDを用いた高速計算入門ARM CPUにおけるSIMDを用いた高速計算入門
ARM CPUにおけるSIMDを用いた高速計算入門Fixstars Corporation
 
x86x64 SSE4.2 POPCNT
x86x64 SSE4.2 POPCNTx86x64 SSE4.2 POPCNT
x86x64 SSE4.2 POPCNTtakesako
 
プログラミングコンテストでのデータ構造
プログラミングコンテストでのデータ構造プログラミングコンテストでのデータ構造
プログラミングコンテストでのデータ構造Takuya Akiba
 
Deflate
DeflateDeflate
Deflate7shi
 
WASM(WebAssembly)入門 ペアリング演算やってみた
WASM(WebAssembly)入門 ペアリング演算やってみたWASM(WebAssembly)入門 ペアリング演算やってみた
WASM(WebAssembly)入門 ペアリング演算やってみたMITSUNARI Shigeo
 
最小カットを使って「燃やす埋める問題」を解く
最小カットを使って「燃やす埋める問題」を解く最小カットを使って「燃やす埋める問題」を解く
最小カットを使って「燃やす埋める問題」を解くshindannin
 
C/C++プログラマのための開発ツール
C/C++プログラマのための開発ツールC/C++プログラマのための開発ツール
C/C++プログラマのための開発ツールMITSUNARI Shigeo
 
暗号技術の実装と数学
暗号技術の実装と数学暗号技術の実装と数学
暗号技術の実装と数学MITSUNARI Shigeo
 
並列化による高速化
並列化による高速化 並列化による高速化
並列化による高速化 sakura-mike
 

What's hot (20)

高速な倍精度指数関数expの実装
高速な倍精度指数関数expの実装高速な倍精度指数関数expの実装
高速な倍精度指数関数expの実装
 
暗号文のままで計算しよう - 準同型暗号入門 -
暗号文のままで計算しよう - 準同型暗号入門 -暗号文のままで計算しよう - 準同型暗号入門 -
暗号文のままで計算しよう - 準同型暗号入門 -
 
C++ マルチスレッド 入門
C++ マルチスレッド 入門C++ マルチスレッド 入門
C++ マルチスレッド 入門
 
Pythonの理解を試みる 〜バイトコードインタプリタを作成する〜
Pythonの理解を試みる 〜バイトコードインタプリタを作成する〜Pythonの理解を試みる 〜バイトコードインタプリタを作成する〜
Pythonの理解を試みる 〜バイトコードインタプリタを作成する〜
 
LLVM最適化のこつ
LLVM最適化のこつLLVM最適化のこつ
LLVM最適化のこつ
 
llvm入門
llvm入門llvm入門
llvm入門
 
冬のLock free祭り safe
冬のLock free祭り safe冬のLock free祭り safe
冬のLock free祭り safe
 
目grep入門 +解説
目grep入門 +解説目grep入門 +解説
目grep入門 +解説
 
AVX2時代の正規表現マッチング 〜半群でぐんぐん!〜
AVX2時代の正規表現マッチング 〜半群でぐんぐん!〜AVX2時代の正規表現マッチング 〜半群でぐんぐん!〜
AVX2時代の正規表現マッチング 〜半群でぐんぐん!〜
 
x86とコンテキストスイッチ
x86とコンテキストスイッチx86とコンテキストスイッチ
x86とコンテキストスイッチ
 
暗号化したまま計算できる暗号技術とOSS開発による広がり
暗号化したまま計算できる暗号技術とOSS開発による広がり暗号化したまま計算できる暗号技術とOSS開発による広がり
暗号化したまま計算できる暗号技術とOSS開発による広がり
 
ARM CPUにおけるSIMDを用いた高速計算入門
ARM CPUにおけるSIMDを用いた高速計算入門ARM CPUにおけるSIMDを用いた高速計算入門
ARM CPUにおけるSIMDを用いた高速計算入門
 
x86x64 SSE4.2 POPCNT
x86x64 SSE4.2 POPCNTx86x64 SSE4.2 POPCNT
x86x64 SSE4.2 POPCNT
 
プログラミングコンテストでのデータ構造
プログラミングコンテストでのデータ構造プログラミングコンテストでのデータ構造
プログラミングコンテストでのデータ構造
 
Deflate
DeflateDeflate
Deflate
 
WASM(WebAssembly)入門 ペアリング演算やってみた
WASM(WebAssembly)入門 ペアリング演算やってみたWASM(WebAssembly)入門 ペアリング演算やってみた
WASM(WebAssembly)入門 ペアリング演算やってみた
 
最小カットを使って「燃やす埋める問題」を解く
最小カットを使って「燃やす埋める問題」を解く最小カットを使って「燃やす埋める問題」を解く
最小カットを使って「燃やす埋める問題」を解く
 
C/C++プログラマのための開発ツール
C/C++プログラマのための開発ツールC/C++プログラマのための開発ツール
C/C++プログラマのための開発ツール
 
暗号技術の実装と数学
暗号技術の実装と数学暗号技術の実装と数学
暗号技術の実装と数学
 
並列化による高速化
並列化による高速化 並列化による高速化
並列化による高速化
 

Similar to フラグを愛でる

Haswellサーベイと有限体クラスの紹介
Haswellサーベイと有限体クラスの紹介Haswellサーベイと有限体クラスの紹介
Haswellサーベイと有限体クラスの紹介MITSUNARI Shigeo
 
Intel AVX-512/富岳SVE用SIMDコード生成ライブラリsimdgen
Intel AVX-512/富岳SVE用SIMDコード生成ライブラリsimdgenIntel AVX-512/富岳SVE用SIMDコード生成ライブラリsimdgen
Intel AVX-512/富岳SVE用SIMDコード生成ライブラリsimdgenMITSUNARI Shigeo
 
Halide による画像処理プログラミング入門
Halide による画像処理プログラミング入門Halide による画像処理プログラミング入門
Halide による画像処理プログラミング入門Fixstars Corporation
 
x64 のスカラー,SIMD 演算性能を測ってみた @ C++ MIX #10
x64 のスカラー,SIMD 演算性能を測ってみた @ C++  MIX #10x64 のスカラー,SIMD 演算性能を測ってみた @ C++  MIX #10
x64 のスカラー,SIMD 演算性能を測ってみた @ C++ MIX #10Muneyoshi Suzuki
 
Wavelet matrix implementation
Wavelet matrix implementationWavelet matrix implementation
Wavelet matrix implementationMITSUNARI Shigeo
 
x64 のスカラー,SIMD 演算性能を測ってみた v0.1 @ C++ MIX #10
x64 のスカラー,SIMD 演算性能を測ってみた v0.1 @ C++  MIX #10x64 のスカラー,SIMD 演算性能を測ってみた v0.1 @ C++  MIX #10
x64 のスカラー,SIMD 演算性能を測ってみた v0.1 @ C++ MIX #10Muneyoshi Suzuki
 
C++ マルチスレッドプログラミング
C++ マルチスレッドプログラミングC++ マルチスレッドプログラミング
C++ マルチスレッドプログラミングKohsuke Yuasa
 
Common LispでGPGPU
Common LispでGPGPUCommon LispでGPGPU
Common LispでGPGPUgos-k
 
LLVMで遊ぶ(整数圧縮とか、x86向けの自動ベクトル化とか)
LLVMで遊ぶ(整数圧縮とか、x86向けの自動ベクトル化とか)LLVMで遊ぶ(整数圧縮とか、x86向けの自動ベクトル化とか)
LLVMで遊ぶ(整数圧縮とか、x86向けの自動ベクトル化とか)Takeshi Yamamuro
 
並列対決 Elixir × Go × C# x Scala , Node.js
並列対決 Elixir × Go × C# x Scala , Node.js並列対決 Elixir × Go × C# x Scala , Node.js
並列対決 Elixir × Go × C# x Scala , Node.jsYoshiiro Ueno
 
つくってあそぼ ラムダ計算インタプリタ
つくってあそぼ ラムダ計算インタプリタつくってあそぼ ラムダ計算インタプリタ
つくってあそぼ ラムダ計算インタプリタ京大 マイコンクラブ
 

Similar to フラグを愛でる (20)

Prosym2012
Prosym2012Prosym2012
Prosym2012
 
Haswellサーベイと有限体クラスの紹介
Haswellサーベイと有限体クラスの紹介Haswellサーベイと有限体クラスの紹介
Haswellサーベイと有限体クラスの紹介
 
From IA-32 to avx-512
From IA-32 to avx-512From IA-32 to avx-512
From IA-32 to avx-512
 
HPC Phys-20201203
HPC Phys-20201203HPC Phys-20201203
HPC Phys-20201203
 
Slide
SlideSlide
Slide
 
Intel AVX-512/富岳SVE用SIMDコード生成ライブラリsimdgen
Intel AVX-512/富岳SVE用SIMDコード生成ライブラリsimdgenIntel AVX-512/富岳SVE用SIMDコード生成ライブラリsimdgen
Intel AVX-512/富岳SVE用SIMDコード生成ライブラリsimdgen
 
optimal Ate pairing
optimal Ate pairingoptimal Ate pairing
optimal Ate pairing
 
Halide による画像処理プログラミング入門
Halide による画像処理プログラミング入門Halide による画像処理プログラミング入門
Halide による画像処理プログラミング入門
 
TVM の紹介
TVM の紹介TVM の紹介
TVM の紹介
 
Boost Tour 1.50.0 All
Boost Tour 1.50.0 AllBoost Tour 1.50.0 All
Boost Tour 1.50.0 All
 
x64 のスカラー,SIMD 演算性能を測ってみた @ C++ MIX #10
x64 のスカラー,SIMD 演算性能を測ってみた @ C++  MIX #10x64 のスカラー,SIMD 演算性能を測ってみた @ C++  MIX #10
x64 のスカラー,SIMD 演算性能を測ってみた @ C++ MIX #10
 
boost tour 1.48.0 all
boost tour 1.48.0 allboost tour 1.48.0 all
boost tour 1.48.0 all
 
Wavelet matrix implementation
Wavelet matrix implementationWavelet matrix implementation
Wavelet matrix implementation
 
x64 のスカラー,SIMD 演算性能を測ってみた v0.1 @ C++ MIX #10
x64 のスカラー,SIMD 演算性能を測ってみた v0.1 @ C++  MIX #10x64 のスカラー,SIMD 演算性能を測ってみた v0.1 @ C++  MIX #10
x64 のスカラー,SIMD 演算性能を測ってみた v0.1 @ C++ MIX #10
 
Objc lambda
Objc lambdaObjc lambda
Objc lambda
 
C++ マルチスレッドプログラミング
C++ マルチスレッドプログラミングC++ マルチスレッドプログラミング
C++ マルチスレッドプログラミング
 
Common LispでGPGPU
Common LispでGPGPUCommon LispでGPGPU
Common LispでGPGPU
 
LLVMで遊ぶ(整数圧縮とか、x86向けの自動ベクトル化とか)
LLVMで遊ぶ(整数圧縮とか、x86向けの自動ベクトル化とか)LLVMで遊ぶ(整数圧縮とか、x86向けの自動ベクトル化とか)
LLVMで遊ぶ(整数圧縮とか、x86向けの自動ベクトル化とか)
 
並列対決 Elixir × Go × C# x Scala , Node.js
並列対決 Elixir × Go × C# x Scala , Node.js並列対決 Elixir × Go × C# x Scala , Node.js
並列対決 Elixir × Go × C# x Scala , Node.js
 
つくってあそぼ ラムダ計算インタプリタ
つくってあそぼ ラムダ計算インタプリタつくってあそぼ ラムダ計算インタプリタ
つくってあそぼ ラムダ計算インタプリタ
 

More from MITSUNARI Shigeo

範囲証明つき準同型暗号とその対話的プロトコル
範囲証明つき準同型暗号とその対話的プロトコル範囲証明つき準同型暗号とその対話的プロトコル
範囲証明つき準同型暗号とその対話的プロトコルMITSUNARI Shigeo
 
暗認本読書会13 advanced
暗認本読書会13 advanced暗認本読書会13 advanced
暗認本読書会13 advancedMITSUNARI Shigeo
 
深層学習フレームワークにおけるIntel CPU/富岳向け最適化法
深層学習フレームワークにおけるIntel CPU/富岳向け最適化法深層学習フレームワークにおけるIntel CPU/富岳向け最適化法
深層学習フレームワークにおけるIntel CPU/富岳向け最適化法MITSUNARI Shigeo
 
WebAssembly向け多倍長演算の実装
WebAssembly向け多倍長演算の実装WebAssembly向け多倍長演算の実装
WebAssembly向け多倍長演算の実装MITSUNARI Shigeo
 
Lifted-ElGamal暗号を用いた任意関数演算の二者間秘密計算プロトコルのmaliciousモデルにおける効率化
Lifted-ElGamal暗号を用いた任意関数演算の二者間秘密計算プロトコルのmaliciousモデルにおける効率化Lifted-ElGamal暗号を用いた任意関数演算の二者間秘密計算プロトコルのmaliciousモデルにおける効率化
Lifted-ElGamal暗号を用いた任意関数演算の二者間秘密計算プロトコルのmaliciousモデルにおける効率化MITSUNARI Shigeo
 
BLS署名の実装とその応用
BLS署名の実装とその応用BLS署名の実装とその応用
BLS署名の実装とその応用MITSUNARI Shigeo
 
LazyFP vulnerabilityの紹介
LazyFP vulnerabilityの紹介LazyFP vulnerabilityの紹介
LazyFP vulnerabilityの紹介MITSUNARI Shigeo
 
ElGamal型暗号文に対する任意関数演算・再暗号化の二者間秘密計算プロトコルとその応用
ElGamal型暗号文に対する任意関数演算・再暗号化の二者間秘密計算プロトコルとその応用ElGamal型暗号文に対する任意関数演算・再暗号化の二者間秘密計算プロトコルとその応用
ElGamal型暗号文に対する任意関数演算・再暗号化の二者間秘密計算プロトコルとその応用MITSUNARI Shigeo
 

More from MITSUNARI Shigeo (20)

範囲証明つき準同型暗号とその対話的プロトコル
範囲証明つき準同型暗号とその対話的プロトコル範囲証明つき準同型暗号とその対話的プロトコル
範囲証明つき準同型暗号とその対話的プロトコル
 
暗認本読書会13 advanced
暗認本読書会13 advanced暗認本読書会13 advanced
暗認本読書会13 advanced
 
暗認本読書会12
暗認本読書会12暗認本読書会12
暗認本読書会12
 
暗認本読書会11
暗認本読書会11暗認本読書会11
暗認本読書会11
 
暗認本読書会10
暗認本読書会10暗認本読書会10
暗認本読書会10
 
暗認本読書会9
暗認本読書会9暗認本読書会9
暗認本読書会9
 
暗認本読書会8
暗認本読書会8暗認本読書会8
暗認本読書会8
 
暗認本読書会7
暗認本読書会7暗認本読書会7
暗認本読書会7
 
暗認本読書会6
暗認本読書会6暗認本読書会6
暗認本読書会6
 
暗認本読書会5
暗認本読書会5暗認本読書会5
暗認本読書会5
 
暗認本読書会4
暗認本読書会4暗認本読書会4
暗認本読書会4
 
深層学習フレームワークにおけるIntel CPU/富岳向け最適化法
深層学習フレームワークにおけるIntel CPU/富岳向け最適化法深層学習フレームワークにおけるIntel CPU/富岳向け最適化法
深層学習フレームワークにおけるIntel CPU/富岳向け最適化法
 
私とOSSの25年
私とOSSの25年私とOSSの25年
私とOSSの25年
 
WebAssembly向け多倍長演算の実装
WebAssembly向け多倍長演算の実装WebAssembly向け多倍長演算の実装
WebAssembly向け多倍長演算の実装
 
Lifted-ElGamal暗号を用いた任意関数演算の二者間秘密計算プロトコルのmaliciousモデルにおける効率化
Lifted-ElGamal暗号を用いた任意関数演算の二者間秘密計算プロトコルのmaliciousモデルにおける効率化Lifted-ElGamal暗号を用いた任意関数演算の二者間秘密計算プロトコルのmaliciousモデルにおける効率化
Lifted-ElGamal暗号を用いた任意関数演算の二者間秘密計算プロトコルのmaliciousモデルにおける効率化
 
楕円曲線と暗号
楕円曲線と暗号楕円曲線と暗号
楕円曲線と暗号
 
BLS署名の実装とその応用
BLS署名の実装とその応用BLS署名の実装とその応用
BLS署名の実装とその応用
 
LazyFP vulnerabilityの紹介
LazyFP vulnerabilityの紹介LazyFP vulnerabilityの紹介
LazyFP vulnerabilityの紹介
 
ゆるバグ
ゆるバグゆるバグ
ゆるバグ
 
ElGamal型暗号文に対する任意関数演算・再暗号化の二者間秘密計算プロトコルとその応用
ElGamal型暗号文に対する任意関数演算・再暗号化の二者間秘密計算プロトコルとその応用ElGamal型暗号文に対する任意関数演算・再暗号化の二者間秘密計算プロトコルとその応用
ElGamal型暗号文に対する任意関数演算・再暗号化の二者間秘密計算プロトコルとその応用
 

Recently uploaded

AWS の OpenShift サービス (ROSA) を使った OpenShift Virtualizationの始め方.pdf
AWS の OpenShift サービス (ROSA) を使った OpenShift Virtualizationの始め方.pdfAWS の OpenShift サービス (ROSA) を使った OpenShift Virtualizationの始め方.pdf
AWS の OpenShift サービス (ROSA) を使った OpenShift Virtualizationの始め方.pdfFumieNakayama
 
自分史上一番早い2024振り返り〜コロナ後、仕事は通常ペースに戻ったか〜 by IoT fullstack engineer
自分史上一番早い2024振り返り〜コロナ後、仕事は通常ペースに戻ったか〜 by IoT fullstack engineer自分史上一番早い2024振り返り〜コロナ後、仕事は通常ペースに戻ったか〜 by IoT fullstack engineer
自分史上一番早い2024振り返り〜コロナ後、仕事は通常ペースに戻ったか〜 by IoT fullstack engineerYuki Kikuchi
 
NewSQLの可用性構成パターン(OCHaCafe Season 8 #4 発表資料)
NewSQLの可用性構成パターン(OCHaCafe Season 8 #4 発表資料)NewSQLの可用性構成パターン(OCHaCafe Season 8 #4 発表資料)
NewSQLの可用性構成パターン(OCHaCafe Season 8 #4 発表資料)NTT DATA Technology & Innovation
 
業務で生成AIを活用したい人のための生成AI入門講座(社外公開版:キンドリルジャパン社内勉強会:2024年4月発表)
業務で生成AIを活用したい人のための生成AI入門講座(社外公開版:キンドリルジャパン社内勉強会:2024年4月発表)業務で生成AIを活用したい人のための生成AI入門講座(社外公開版:キンドリルジャパン社内勉強会:2024年4月発表)
業務で生成AIを活用したい人のための生成AI入門講座(社外公開版:キンドリルジャパン社内勉強会:2024年4月発表)Hiroshi Tomioka
 
デジタル・フォレンジックの最新動向(2024年4月27日情洛会総会特別講演スライド)
デジタル・フォレンジックの最新動向(2024年4月27日情洛会総会特別講演スライド)デジタル・フォレンジックの最新動向(2024年4月27日情洛会総会特別講演スライド)
デジタル・フォレンジックの最新動向(2024年4月27日情洛会総会特別講演スライド)UEHARA, Tetsutaro
 
モーダル間の変換後の一致性とジャンル表を用いた解釈可能性の考察 ~Text-to-MusicとText-To-ImageかつImage-to-Music...
モーダル間の変換後の一致性とジャンル表を用いた解釈可能性の考察  ~Text-to-MusicとText-To-ImageかつImage-to-Music...モーダル間の変換後の一致性とジャンル表を用いた解釈可能性の考察  ~Text-to-MusicとText-To-ImageかつImage-to-Music...
モーダル間の変換後の一致性とジャンル表を用いた解釈可能性の考察 ~Text-to-MusicとText-To-ImageかつImage-to-Music...博三 太田
 
CTO, VPoE, テックリードなどリーダーポジションに登用したくなるのはどんな人材か?
CTO, VPoE, テックリードなどリーダーポジションに登用したくなるのはどんな人材か?CTO, VPoE, テックリードなどリーダーポジションに登用したくなるのはどんな人材か?
CTO, VPoE, テックリードなどリーダーポジションに登用したくなるのはどんな人材か?akihisamiyanaga1
 
クラウドネイティブなサーバー仮想化基盤 - OpenShift Virtualization.pdf
クラウドネイティブなサーバー仮想化基盤 - OpenShift Virtualization.pdfクラウドネイティブなサーバー仮想化基盤 - OpenShift Virtualization.pdf
クラウドネイティブなサーバー仮想化基盤 - OpenShift Virtualization.pdfFumieNakayama
 
TataPixel: 畳の異方性を利用した切り替え可能なディスプレイの提案
TataPixel: 畳の異方性を利用した切り替え可能なディスプレイの提案TataPixel: 畳の異方性を利用した切り替え可能なディスプレイの提案
TataPixel: 畳の異方性を利用した切り替え可能なディスプレイの提案sugiuralab
 

Recently uploaded (9)

AWS の OpenShift サービス (ROSA) を使った OpenShift Virtualizationの始め方.pdf
AWS の OpenShift サービス (ROSA) を使った OpenShift Virtualizationの始め方.pdfAWS の OpenShift サービス (ROSA) を使った OpenShift Virtualizationの始め方.pdf
AWS の OpenShift サービス (ROSA) を使った OpenShift Virtualizationの始め方.pdf
 
自分史上一番早い2024振り返り〜コロナ後、仕事は通常ペースに戻ったか〜 by IoT fullstack engineer
自分史上一番早い2024振り返り〜コロナ後、仕事は通常ペースに戻ったか〜 by IoT fullstack engineer自分史上一番早い2024振り返り〜コロナ後、仕事は通常ペースに戻ったか〜 by IoT fullstack engineer
自分史上一番早い2024振り返り〜コロナ後、仕事は通常ペースに戻ったか〜 by IoT fullstack engineer
 
NewSQLの可用性構成パターン(OCHaCafe Season 8 #4 発表資料)
NewSQLの可用性構成パターン(OCHaCafe Season 8 #4 発表資料)NewSQLの可用性構成パターン(OCHaCafe Season 8 #4 発表資料)
NewSQLの可用性構成パターン(OCHaCafe Season 8 #4 発表資料)
 
業務で生成AIを活用したい人のための生成AI入門講座(社外公開版:キンドリルジャパン社内勉強会:2024年4月発表)
業務で生成AIを活用したい人のための生成AI入門講座(社外公開版:キンドリルジャパン社内勉強会:2024年4月発表)業務で生成AIを活用したい人のための生成AI入門講座(社外公開版:キンドリルジャパン社内勉強会:2024年4月発表)
業務で生成AIを活用したい人のための生成AI入門講座(社外公開版:キンドリルジャパン社内勉強会:2024年4月発表)
 
デジタル・フォレンジックの最新動向(2024年4月27日情洛会総会特別講演スライド)
デジタル・フォレンジックの最新動向(2024年4月27日情洛会総会特別講演スライド)デジタル・フォレンジックの最新動向(2024年4月27日情洛会総会特別講演スライド)
デジタル・フォレンジックの最新動向(2024年4月27日情洛会総会特別講演スライド)
 
モーダル間の変換後の一致性とジャンル表を用いた解釈可能性の考察 ~Text-to-MusicとText-To-ImageかつImage-to-Music...
モーダル間の変換後の一致性とジャンル表を用いた解釈可能性の考察  ~Text-to-MusicとText-To-ImageかつImage-to-Music...モーダル間の変換後の一致性とジャンル表を用いた解釈可能性の考察  ~Text-to-MusicとText-To-ImageかつImage-to-Music...
モーダル間の変換後の一致性とジャンル表を用いた解釈可能性の考察 ~Text-to-MusicとText-To-ImageかつImage-to-Music...
 
CTO, VPoE, テックリードなどリーダーポジションに登用したくなるのはどんな人材か?
CTO, VPoE, テックリードなどリーダーポジションに登用したくなるのはどんな人材か?CTO, VPoE, テックリードなどリーダーポジションに登用したくなるのはどんな人材か?
CTO, VPoE, テックリードなどリーダーポジションに登用したくなるのはどんな人材か?
 
クラウドネイティブなサーバー仮想化基盤 - OpenShift Virtualization.pdf
クラウドネイティブなサーバー仮想化基盤 - OpenShift Virtualization.pdfクラウドネイティブなサーバー仮想化基盤 - OpenShift Virtualization.pdf
クラウドネイティブなサーバー仮想化基盤 - OpenShift Virtualization.pdf
 
TataPixel: 畳の異方性を利用した切り替え可能なディスプレイの提案
TataPixel: 畳の異方性を利用した切り替え可能なディスプレイの提案TataPixel: 畳の異方性を利用した切り替え可能なディスプレイの提案
TataPixel: 畳の異方性を利用した切り替え可能なディスプレイの提案
 

フラグを愛でる

  • 2. 目次  さまざまなコード片でcarryの扱いを味わってみる  フラグの復習  絶対値の復習  ビットの長さ  ビットカウント cybozu/WaveletMatrixの紹介  多倍長整数加算  Haswellで追加された命令達 題材があちこち飛びます m(__)m 2013/3/30 #x86opti 5 2 /24
  • 3. フラグの復習  演算ごとに変化する1bitの情報群  よく使うもの  ZF : Zero flag : 演算結果が0ならtrue  CF : Carry flag : 演算に桁上がり、桁借りがあればtrue  SF : Sign flag : 演算結果が負ならtrue 例 mov eax, 5 neg eax ; -5になるのでSF = 1, ZF = 0 mov eax, 0x80000001 mov ecx, 0x80000002 add eax, ecx ; 32bitを超えるのでCF = 1  条件つきmov命令  フラグの条件が成立すれば代入 cmovz eax, ecx ; ZF = 1ならeax ← ecx 2013/3/30 #x86opti 5 3 /24
  • 4. 絶対値(1/3)  int abs(int x) { return x >= 0 ? x : -x; }  -x = ~x + 1  ~x = x ^ (-1)  組み合わせると –x = x ^ (-1) – (-1)  またx ^ 0 – 0 = x  xが0以上の時も、xが負のときもx ^ M – Mの形をしている  Mを作れればabsを分岐なしで実行できる 古典的なトリック int abs(int x) { uint32_t M = (x >> 31); // M = x >= 0 ? 0 : (-1); return x ^ M – M; } 2013/3/30 #x86opti 5 4 /24
  • 5. 絶対値(2/3)  gcc 4.7での実装 // input : ecx, output : eax // destroy : edx, eax mov edx, ecx ; edx = x sar edx, 31 ; edx = (x < 0) ? -1 : 0; mov eax, edx ; eax = M xor eax, ecx ; eax ^= x ; eax = x ^ M sub eax, edx ; eax -= M ; eax = x ^ M - M  clang 3.3での実装 mov eax, ecx ; eax = x mov edx, ecx ; edx = x neg eax ; eax = -x cmovl eax, edx ; eax = (x > 0) ? x : -x  わかりやすい  sandyだと少し速い(古いCPUだとgccのがよいことも) 2013/3/30 #x86opti 5 5 /24
  • 6. 絶対値(3/3)  VCでの実装 mov eax, ecx cdq ; M = edx = (eax < 0) ? -1 : 0 xor eax, edx ; eax = x^ M sub eax, edx ; eax = x^ M - M  cmovより速い(ことが多い) cmov命令はIntel CPUではものすごく速いというわけではない core2duo, sandy bridgeと段々よくなったが  レジスタ固定だが  まあいまどきmovはほぼコスト0だし  64bitだとcqo(0x48 0x99) VCのcod出力はcdqと表示されるので注意 2013/3/30 #x86opti 5 6 /24
  • 7. ビットの長さ(1/4)  xを確保するのに必要なビットの長さ  x == 0のとき1とする int bitLen(uint32_t x) { if (x == 0) return 1; for (int i = 0; i < 32; i++) { if (x < (1u << i)) return i; } return 32; }  __builtin_clzを使う  これはcount leading zeroなので32から結果を引く  この関数はx == 0のときは未定義なので別に処理する if (x == 0) return 1; return 32 - __builtin_clz(x); 2013/3/30 #x86opti 5 7 /24
  • 8. ビットの長さ(2/4)  gcc 4.7とclang 3.3 // gcc // clang test edi, edi mov eax, 1 mov eax, 1 test edi, edi je .Z je .Z bsr edi, edi bsr eax, edi mov al, 32 xor eax, -32 xor edi, 31 add eax, 33 sub eax, edi .Z: ret .Z: ret  clangの方がちょっと賢い感じだがなんか微妙  bsr(x) == 32 - __builin_clz(x)なので回りくどい 少し変えてみる if (x == 0) return 1; // return 32 - __builtin_clz(x); return (__builtin_clz(x) ^ 0x1f) + 1; 2013/3/30 #x86opti 5 8 /24
  • 9. ビットの長さ(3/4)  なぜかgccだけよくなった  xorとaddがキャンセルした // gcc // clang test edi, edi mov eax, 1 mov eax, 1 test edi, edi je .Zero je .Zero bsr edi, edi bsr eax, edi add eax, 1 xor eax, -32 add eax, 33 .Zero: ret .Zero: ret  VCでは_BitScanReverse(&ret, x)を使う  これはx == 0のときfalseを返す unsigned long ret; if (_BitScanReverse(&ret, x)) return ret + 1; return 1; 2013/3/30 #x86opti 5 9 /24
  • 10. ビットの長さ(4/4)  VCはbsrは入力が0ならZF=1なことを知っている bsr eax, ecx je .zero inc eax ret .zero: mov eax, 1 ret  いや, でもZF = 1のときはecx = 0なんだし こうすればすっきりする bsr eax, ecx cmovz eax, ecx inc eax ret  ただしx == 0が殆どありえないなら上の方が速いかも 2013/3/30 #x86opti 5 10 /24
  • 11. 減算のあとのmod p(1/2)  暗号ではX={0, 1, ..., p-1}の中の四則演算をよく使う  x, y ∈ Xに対して(x + y) % pとか(x – y) % pとか // 引き算の疑似コード // const uint255_t p = ...; uint255_t sub(uint255_t x, uint255_t y) { if (x >= y) return x – y; return x + p – y; }  大小比較って結局は引いてみないと分からない uint255_t add(uint255_t x, uint255_t y) { int256_t t = x – y; if (t < 0) t += p; return t; }  分岐はランダムなので10clk以内なら条件jmpは避けたい 2013/3/30 #x86opti 5 11 /24
  • 12. 減算のあとのmod p(2/2)  sub + sbb後のCFを利用してaddすべき値をcmov // 疑似コード sub x0, y0 sbb x1, y1 sbb x2, y2 sbb x3, y3 // [x3:x2:x1:x0] – [y3:y2:y1:y0] t0 = t1 = t2 = t3 = 0; cmovc [t3:t2:t1:t0], [p3:p2:p1:p0] ; t = (x < y) ? p : 0 [x3:x2:x1:x] += [t3:t2:t1:t0]  256bit減算なので64bitレジスタ x 4を使う 実際にはcmovなどが4個並んでる  ルール : 分岐予測不可→cmov→可能なら単純命令に  0に設定してcmovよりマスクして&が少し速い(CPUによる) sbb m, m // m = (x < y) ? -1 : 0 [t3:t2:t1:t0] = [p3:p2:p1:p0] [t3:t2:t1:t0] &= m 2013/3/30 #x86opti 5 12 /24
  • 13. 128bit popcnt(1/3)  Wavelet行列の中で使う簡潔ベクトル構造の中  結局, 今のところ使わなかったけど面白かったので紹介 idxから128bit分のマスクを作って[b1:b0]と&をとってpopcnt idx&127>=64なら[m1:m0]=[*:-1]。<64なら[m1:m0]=[0:*] | b0 | b1 | |0|1|2|3|4|5|6|7|8|9|a|b|c|d|e|f| m|***************|**** | idx & 127 >= 64 |********** | | idx & 127 < 64 | m0 | m1 | uint64_t maskAndPopcnt(uint64_t b0, uint64_t b1, uint64_t idx){ const uint64_t mask = (uint64_t(1) << (idx & 63)) - 1; uint64_t m0 = (idx & 64) ? -1 : mask; uint64_t m1 = (idx & 64) ? mask : 0; uint64_t ret = popcnt(b0 & m0); ret += popcnt(b1 & m1); return ret; } 2013/3/30 #x86opti 5 13 /24
  • 14. 128bit popcnt(2/3)  gcc 4.7  ジャンプ命令を使う…論外  VC2012  idx & 64 == 0の判定を2回する…おしい  clang 3.3  idx & 64 == 0の判定は1回だがなぜかシフト しかも作ったフラグをつぶす(clangあるある) // edx = idx ZFを保存するため 最適解? and edx, 64 順序入れ換える ecxとedxを入れ換えたら shr edx, 6 xor不要 xor ecx, ecx xor ecx, ecx or rcx, -1 ;3byte減る test edx, edx and edx, 6 and edx, 6 cmovneq rcx, rax cmovneq rcx,rax cmovneq rdx, rax mov rdx, -1 mov rdx, -1 cmoveq rdx, rax cmoveq rdx, rax cmoveq rcx, rax 2013/3/30 #x86opti 5 14 /24
  • 15. 128bit popcnt(3/3)  ちょっと宣伝  SucVectorとWaveletMatrixクラス開発中 実装済みメソッド : get, rank, rankLt, select Yasuo.Tabeiさんのfmindex++に組み込んでみた 実験コード https://github.com/herumi/fmindex  200MBのUTF-8テキストから1400個の単語(平均12byte) の全出現位置列挙(locateの呼び出し24M回) 実装 時間[sec] lookup[clk] rank[clk] オリジナル(wat_array) 160 1887 1887 wavelet-matrix-cpp(wm) 72 883 598 cybozu/WaveletMatrix(cy) 30 343 183 wat_arrayは岡野原さん, wavelet-matrix-cppはmanabeさん作 wmに比べてもcyはrankが約3.2倍, lookupが2.5倍 2013/3/30 #x86opti 5 15 /24
  • 16. 多倍長整数の加算(1/5)  前半発表のlliの出力(一部) .lp: mov r9, qword [rsi] ; x[i] add r9, qword [rdx] ; +y[i] setb al ; al = carry ? 1 : 0 movzx r8d, r8b ; 一つ目のcarry and r8, 1 add r8, r9 ; x[i] + y[i] + carry mov qword [rdi], r8 ; 保存 add rsi, 8 add rdx, 8 add rdi, 8 dec rcx ; ループカウンタ mov r8b, al ; 今回のフラグを保存 jne .lp  なんかえらいことに  実はとても遅いというわけではなかったり(たまたま) 2013/3/30 #x86opti 5 16 /24
  • 17. 多倍長整数の加算(2/5)  加算でやらしいところ  carryつきaddを実行したいのでcarryを変更してはいけない  でもループ変数はいじらないといけない  コンパイラに任せると先程のフラグを保存するコードになる  抜け道  add, sub, adc, sbbはCFとZFを変更するが inc, decはCFを変更しない  ループカウンタcを-nから0方向にインクリメント x, y, zのアドレスはあらかじめn * 8を足してずらしておく .lp: mov(t, ptr [x + c * 8]); // t = x[i] adc(t, ptr [y + c * 8]); // t = x[i] + y[i] + carry mov(ptr [z + c * 8], t); // z[i] = t inc(c); jnz(".lp"); 2013/3/30 #x86opti 5 17 /24
  • 18. 多倍長整数の加算(3/5)  Xeon X5650(Westmere)では  ループあたり13clkもかかる なんと先程のLLVMの(酷い)コードよりも遅い!  フラグに関するパーシャルレジスタストール Intelの最適化マニュアル 「INCとDECはADDやSUBに置き換えるべきだ」 置き換えたら動かないんですけど .lp: mov(t, ptr [x + rcx * 8]); // t = x[i] adc(t, ptr [y + rcx * 8]); // t = x[i] + y[i] + carry mov(ptr [z + rcx * 8], t); // z[i] = t sub(c, 1); // adcのキャリーを破壊する jnz(".lp"); 2013/3/30 #x86opti 5 18 /24
  • 19. 多倍長整数の加算(4/5)  jrcxz/jecxz命令  rcx, ecxがゼロなら分岐する命令 みなさん覚えてますか? 私は忘れてました  Pentiumで遅かったのでloop命令とともに封印された(私の中で) jnrcxzは無いのでループで使うとねじれるのが難  core2duo以降はそこそこ速い .lp: jrcxz(".exit"); mov(t, ptr [x + c * 8]); adc(t, ptr [y + c * 8]); mov(ptr [out + c * 8], t); lea(c, ptr [c + 1]); jmp(".lp");  16回ループ(1024bit加算)が208clk→62clk 2013/3/30 #x86opti 5 19 /24
  • 20. 多倍長整数の加算(5/5)  Sandy Bridgeでは改良された  元のコード(adc + dec)の方が速い  先頭だけ外に出す微調整でもっと速くなった(なぜ) 1024bit(64x16)加算 adc + dec LLVM adc + jrcxz adc + dec(その2) Core2Duo(Win) 215 --- 55 221 Xeon X5650(Westmere) 208 63 62 202 sandy bridge 48 64 52 33  https://github.com/herumi/opti/blob/master/uint_add.cpp  多倍長の乗算 read+modify+writeが2clk/64bit  同様にmulがフラグを変更するのが邪魔  レジスタの使い回しで非常に苦労する  速度低下にもつながる 2013/3/30 #x86opti 5 20 /24
  • 21. Haswellで追加された命令(1/3)  無視されるフラグたち 命令 動作 adcx 符号なし加算(CFのみ変更) adox 符号なし加算(OFのみ変更) mulx 符号なし乗算(フラグ変更なし) sarx 算術右シフト(フラグ変更なし) shlx 論理左シフト(フラグ変更なし) shrx 論理右シフト(フラグ変更なし) rorx 論理右回転(フラグ変更なし)  未定義だった部分が確定した命令 lzcnt bsrの拡張 tzcnt bsfの拡張 2013/3/30 #x86opti 5 21 /24
  • 22. Haswellで追加された命令(2/3)  ビット操作系  andn(x, y) = ~x & y 今更感が  bextr(x, start, len) xの[start+len-1:start]のビットを取り出す(範囲外は0拡張)  blsi(x) = x & (-x) 下からみて初めて1となってるビットのみを取り出す  blsr(x) = x & (x-1) 上からみて初めて1となってるビットのみを取り出す  blsmsk(x) = x ^ (x-1) 下からみて初めて1となるところまでのマスクを作る  bzhi(x, n) = x & (~((-1) << n)) nビットより上をクリア 2013/3/30 #x86opti 5 22 /24
  • 23. Haswellで追加された命令(3/3)  pdep(x, mask) maskのビットがたっているところにxのビットを埋め込む x x5 x4 x3 x2 x1 x0 mask 1 1 0 0 1 0 result x2 x1 0 0 x0 0  pext(x, mask) maskのビットが立っているところのxのビットを取り出す x x5 x4 x3 x2 x1 x0 mask 1 1 0 0 1 0 result . . . x5 x4 x1 2013/3/30 #x86opti 5 23 /24
  • 24. おまけ  今どきのコンパイラはかしこい  ハッシュ関数(fnv-1a)のループ内演算  https://github.com/herumi/misc/blob/master/fnv-1a.cpp for (size_t i = 0; i < n; i++) { v ^= x[i]; v += (v<<1)+(v<<4)+(v<<5)+(v<<7)+(v<<8)+(v<<40); }  「v += シフト&加算」の部分はv *= p(41bitの素数)の形 pのハミング重み(2進数展開の1の数)が小さいものを探して選ぶ  gcc 4.7は素直にleaやaddやshlを組み合わせたコード生成  clang, VCはmulに置き換えた! mov r10, 1099511628211 ; p こっちのほうが2.4倍速い@i7 .lp: movzx r8d, byte [r9+rcx] ハミング重みにこだわらない inc r9 関数探索もありか? xor rax, r8 imul rax, r10 2013/3/30 #x86opti 5 24 /24