SlideShare a Scribd company logo
高機能アセンブラによるx86/x64 CPU向
    け高速化テクニック


         Cybozu Labs
       2012/8/24 光成滋生
目次
 x86/x64アセンブラの復習
 C++用のx86/x64アセンブラXbyakの紹介
   サンプル
 文字列検索
 ビットを数える

 自己紹介
  x86/x64最適化勉強会主催
  https://github.com/herumi/opti/
  http://www.slideshare.net/herumi/



夏のプログラムシンポジウム2012                      2 / 33
x86/x64アセンブラの復習(1/2)
 汎用レジスタは15個(64bit時)
  rax, rbx, ..., r8, ..., r15 ; 64bitレジスタ
  rsp : スタックレジスタ
 基本的に2オペランド
  <op> <dst> <src>   //   dst ← op(dst, src);
  dstは破壊される
  アドレッシングは比較的高機能
  ptr [<reg1> + <reg2> * (0|1|2|4|8) + <即値>]

 例)
 mov rax, rcx // rax = rcx;
 add ecx, 4   // ecx += 4;
 sub r8,[rax+ebx*4+12]//r8 -= *(int64_t)(rax+ebx*4+12);
夏のプログラムシンポジウム2012                                         3 / 33
x86/x64アセンブラの復習(2/2)
 SIMDレジスタは16個(64bit時)
  xmm0, ..., xmm15 ; 128bitレジスタ
  ymm0, ..., ymm15 ; 256bitレジスタ
 データ型
  char x 16, int x 4, float x 8, double x 4, etc.
 演算の種類
  四則演算,ビット演算,特殊演算,etc.
  3オペランドタイプもある

 例)
 movaps xmm0,[eax] //xmm0にfloat変数4個が代入される
 vaddpd ymm0,ymm3,ymm2 //ymm0 = ymm3 + ymm2(double x 4)
 pand xmm2, xmm4 //xmm2 &= xmm4
夏のプログラムシンポジウム2012                                    4 / 33
Xbyakの特長
 C++ヘッダのみで記述
  外部ライブラリのビルドやリンクが不要
  対応コンパイラ : Visual Studio/gcc/clang
  対応OS : Windows/Linux/Mac
 実行時コード生成
 Intel MASM形式に似せたDSLを提供
  できるだけ自然に記述できるようにした
  アドレッシングは設計時に一番悩んだ部分
    かつ一番気に入ってる部分

 mov(eax, 5); // mov eax, 5
 add(rcx, byte[eax+ecx*4–5]);//add rcx,byte[eax+ecx*4-5]
 jmp("lp"); // jmp lp
夏のプログラムシンポジウム2012                                      5 / 33
簡単なサンプル(1/3)
 整数nが与えられたときにnを足す関数を生成
  ヘッダをincludeしてCodeGeneratorを継承

 #include <xbyak.h>

 struct Code : Xbyak::CodeGenerator {
   explicit Code(int n) {
     mov(eax, ptr [esp + 4]); // 32bit OS
     add(eax, n);
     // lea(rax, ptr [rcx + n]); // 64bit Windows
     // lea(eax, ptr [edi + n]); // 64bit Linux/Mac
     ret();
   }
 };

夏のプログラムシンポジウム2012                                     6 / 33
簡単なサンプル(2/3)
 インスタンスを生成して実行
 int main(int argc, char *argv[]) {
   const int n = argc == 1 ? 5 : atoi(argv[1]);
   Code c(n);
   auto f = (int (*)(int))c.getCode();
   for (int i = 0; i < 3; i++) {
     printf("%d + %d = %dn", i, n, f(i));
   }
 }

 %   ./a.out 9
 0   + 9 = 9
 1   + 9 = 10
 2   + 9 = 11
夏のプログラムシンポジウム2012                                 7 / 33
簡単なサンプル(3/3)
 インスタンスを生成して実行
  引数が9のときの関数fの中身をデバッガで確認
  32bit Windowsで見てみた
 mov eax,dword ptr [esp+4]
 add eax,9 // ここが即値
 ret
  引数が3のときの関数fの中身をデバッガで確認
  64bit Linuxで見てみた

 0x0000000000607000 in ?? ()
 1: x/i $pc
   0x607000:    lea    eax,[edi+0x3]
                                 // ここが即値

夏のプログラムシンポジウム2012                           8 / 33
応用例
 ビューティフルコード8章
  画像処理のためのその場コード生成
  画像処理では関数内では固定のパラメータが多い
  templateなどを組み合わせるとバイナリサイズが肥大化




夏のプログラムシンポジウム2012                  9 / 33
画像処理のためのその場コード生成
 ビットマップD, Sを合成変換する関数
  変換種類を示すopがループの最内部分にあるため通常の実装
   では遅すぎる
  1985年のWindowsではそのコードを生成するミニコンパイラ
   を含んでいたらしい

 for (int y = 0; y < cy; y++) {
   for (int x = 0; x < cx; x++) {
     switch (op) {
     case 0x00: D[y][x] = 0; break;
     ...
     case 0x60: D[y][x] = (D[y][x] ^ S[y][x]) & P[y][x];
     ...
     } } }
夏のプログラムシンポジウム2012                                     10 / 33
Xbyakによるその場コード生成
 メリット                     Code(const Rect& rect, int op){
                              // generate prolog
  C++の文法でasmの制御構造を L(".lp_y");
   記述できる                      mov(rcx, rect.width);
                           L(".lp_x");
  極めて直感的に扱える
                            switch (op) {
  アセンブラ独自の疑似命令を              ..
    覚える必要がない                  case 0x60:
                                mov(eax,ptr[ptrD+rbx]);
  Cの構造体との連携が                   xor(eax,ptr[ptrS+rbx]);
   シームレス                        and(eax,ptr[ptrP+rbx]);
  構造体メンバのオフセット                 break;
    取得は外部ツールでは困難            }
                            mov(ptr[ptrD + rbx], eax);
  Xbyakなら<stddef.h>の       add(rbx, 4);
    offsetof(type, member)  sub(rcx, 1);
    をそのまま利用可能               jnz(".lp_x");
                              ...

夏のプログラムシンポジウム2012                                      11 / 33
FFTのバタフライ演算(1/2)
 ビット反転とデータ移動    void swap(double*a,int k1,int j1){
                  __m128d x = _mm_load_pd(a + j1);
  入力は次数nとデータa    __m128d y = _mm_load_pd(a + k1);
  ipはビット反転用work  __mm_store_pd(a + j1, y);
                  __mm_store_pd(a + k1, x);
  nは可変だが,128とか  }
   256とかが多いケース   void bitrv(int n,int *ip,double *a)
  通常は事前に専用コード   {
                   int j, j1, k, k1, l, m, m2;
   を用意して分岐する
                   l = n; m = 1;
  ツールでCのコードを生成    ...
    するなど           m2 = 2 * m;
                   if ((m << 3) == l) {
                       for (k = 0; k < m; k++) {
                           for (j = 0; j < k; j++) {
                               j1 = 2 * j + ip[k];
                               k1 = 2 * k + ip[j];
                               swap(a, j1, k1);
夏のプログラムシンポジウム2012                               12 / 33
FFTのバタフライ演算(2/2)
 修正箇所はわずか                    struct Code : Xbyak::CodeGenerator {
  swap()をコード生成                 void swap(const Reg32e& a
                                  , int k1, int j1){
   するように変更                        movapd(xm0, ptr [a + j1 * 8]);
  関数のプロローグと                      movapd(xm1, ptr [a + k1 * 8]);
   エピローグを作る                       movapd(ptr [a + j1 * 8], xm1);
                                  movapd(ptr [a + k1 * 8], xm0);
  生成コード例                       }
  定数とループが全て                    void gen_bitrv2(int n, int *ip){
    展開される                         const Reg64& a = rdi;
                                  int j, j1, k, k1, l, m, m2;
movapd   xm0,ptr [eax+10h]        ...
movapd   xm1,ptr [eax+100h]             j1 = 2 * j + ip[k];
movapd   ptr [eax+10h],xm1              k1 = 2 * k + ip[j];
movapd   ptr [eax+100h],xm0             swap(a, j1, k1);
movapd   xm0,ptr [eax+50h]              ...
movapd   xm1,ptr [eax+140h]
...
夏のプログラムシンポジウム2012                                             13 / 33
SSE4.1の文字列探索命令の紹介
 pcmpestri, pcmpistriなど
  strlenやstrstrなどを高速に実装するための命令群
 複雑なパラメータを持つCISCの権化の様な命令
  文字列処理の単位char or short(2byte:UTF16)の選択
  符号付き・符号無しの選択
  文字列の比較方法の選択
  完全マッチ,文字の範囲指定,部分文字列など
  入力文字列の設定
  0ターミネート文字列 or [begin, end)による文字列
  CF, ZF, SF, OFの各フラグに結果の様々な情報
 詳細
  http://www.slideshare.net/herumi/x86opti3

夏のプログラムシンポジウム2012                              14 / 33
strstrを作ってみる
 16byteずつ文字列を探索する
  入力パラメータ
  a : 入力テキスト(text)ポインタが格納されたレジスタ
  xm0 : 検索文字(の先頭最大16byte)
  12 : 文字列マッチをuint8_tの部分文字マッチさせる即値
  出力フラグ
  CF : 文字列を見つける前にtextが終われば1
  ZF : aから16byte内に文字列がなければ0
  movdqu(xm0, ptr [key]); // xm0 = *key
L(".lp");
  pcmpistri(xm0, ptr [a], 12);
  lea(a, ptr [a + 16]);
  ja(".lp");
  jnc(".notFound");
夏のプログラムシンポジウム2012                         15 / 33
strstrのコア部分(続き)
 keyの先頭文字が一致する部分を検出した
  先程のループを抜けたときはcにマッチしたオフセットが入っ
   ているのでそれだけ進める(add a, c)
  その場所からkeyの最後まで一致しているかを確認
  OF == 0なら一致しなかった
  SF == 1なら見つかった
  add(a, c);
  mov(save_a, a); // save a
  mov(save_key, key); // save key
L(".tailCmp");
  movdqu(xm1, ptr [save_key]);
  pcmpistri(xm1, ptr [save_a], 12);
  jno(".next"); // if (OF == 0) goto .next
  js(".found"); // if (SF == 1) goto .found
  add(save_a, 16);
  add(save_key, 16);
  jmp(".tailCmp");
夏のプログラムシンポジウム2012                             16 / 33
ベンチマーク
 対象CPU, コンパイラ
  Xeon X5650 + gcc 4.6.3
 対象コード
  gccのstrstr(これもSSE4.1を利用している)
  boost 1.51のalgorithm::boyer_moore(BM法)
  Quick Search(改良版BMアルゴリズム)
  my_strstr
 検索方法
  テキスト:130MBのUTF8な日本語を多く含むもの
  指定されたkey文字列がいくつあるかを探す
  byte単位あたりにかかったCPU clock数を表示する


夏のプログラムシンポジウム2012                           17 / 33
strstrのベンチマーク
 Xeon X5650 + gcc 4.6.3
                             8                                               string::find
                             7                                               boost::bm
                                                                                             fast
cycle/Byte to find




                             6                                               quick search
                             5                                               strstr(gcc)
                             4                                               my_strstr
                             3
                             2
                             1
                             0
                                                                static_ass
                                     a     ab     1234   これは                   00...0       AB...Z
                                                                    ert
                     string::find   3.37   3.04   3.23   7.39     2.95          3.27        2.8
                     boost::bm      6.76   6.27   3.23   1.74     1.07          0.93        0.56
                     quick search   4.7    3.12   1.99   3.4      0.85          0.7         0.54
                     strstr(gcc)    1.64   1.13   1.12   1.15     1.11          1.18        0.46
                     my_strstr      0.6    0.3    0.29   0.8      0.28          0.3         0.27
夏のプログラムシンポジウム2012                                                                             18 / 33
考察
 ABC....Zの様なBM法やQuick Searchアルゴリズムにと
  って有利な文字列すらstrstrより遅い
  BM法やQuick Searchはテーブルを引いてオフセットを足すた
   めパイプラインに悪影響がある
  通常の文字列検索では出番がない?
 // Quick Searchの検索部分
 const char *find(const char *begin, const char *end) const {
     while (begin <= end - len_) {
       for (size_t i = 0; i < len_; i++) {
         if (str_[i] != begin[i]) goto NEXT;
       }
       return begin;
     NEXT:
       begin += tbl_[static_cast<unsigned char>(begin[len_])];
     }
     return end;
   }
夏のプログラムシンポジウム2012                                                19 / 33
strcasestrの実装
 keyの大文字小文字を区別しないstrstr
  コードの簡略さのためにkeyは事前に小文字化しておく
 大文字小文字を区別しないマッチ方法
  textを小文字にしてマッチさせる(先程のコードに委譲)
  movdqu(xm0, ptr [key]); // xm0 = *key
L(".lp");
  if (caseInsensitive) {
    movdqu(xm1, ptr [a]);
    toLower(xm1, Am1, Zp1, amA, t0, t1);//小文字化するコード生成関数
    pcmpistri(xm0, xm1, 12);
  } else {
    pcmpistri(xm0, ptr [a], 12);
  }
  lea(a, ptr [a + 16]);
  ja(".lp"); // if (CF == 0 and ZF = 0) goto .lp
  jnc(".notFound");
夏のプログラムシンポジウム2012                                   20 / 33
toLower(1/2)
 大文字を小文字にする
  'A' <= c && c <= 'Z'なら c += 'a' – 'Z'
  分岐無しで16byteずつまとめてやりたい
 pcmpgtb x, y
  x ← x > y ? 0xff : 0;をbyte単位で行う関数
  if ('A' <= c && c <= 'Z') c += 'a' – 'Z';を書き換える
  ('A' <= c && c <= 'Z') ? ('a' – 'Z') : 0;
= ((c > 'A'–1) ? 0xff : 0) & (('Z'+1 > c) ? 0xff : 0) & ('a'–'Z');
= pcmpgtb(c, 'A'-1) & pcmpgtb('Z'+1, c) & 'a'-'Z';




夏のプログラムシンポジウム2012                                               21 / 33
toLower(2/2)
 実際のコード片
/*
     toLower in x
     Am1 : 'A' – 1, Zp1 : 'Z' + 1, amA : 'a' - 'A'
     t0, t1 : temporary register
*/
void toLower(const Xmm& x, const Xmm& Am1, const Xmm& Zp1
    , const Xmm& amA , const Xmm& t0, const Xmm& t1) {
   movdqa(t0, x);
   pcmpgtb(t0, Am1); // -1 if c > 'A' - 1
   movdqa(t1, Zp1);
   pcmpgtb(t1, x); // -1 if 'Z' + 1 > c
   pand(t0, t1); // -1 if [A-Z]
   pand(t0, amA); // 0x20 if c in [A-Z]
   paddb(x, t0); // [A-Z] -> [a-z]
}


夏のプログラムシンポジウム2012                                           22 / 33
CPU判別によるディスパッチ
 コア部分再度(詳細)        pcmpistri(xm0,ptr[a],12);
  実験によるとCPUの世代で     if (isSandyBridge) {
   コードの書き方で速度が違う       lea(a, ptr [a + 16]);
   (lea vs add)        ja(".lp");
                     } else {
  実行時にCPU判別することで
                       jbe(".headCmp");
   適切なコード生成を行う
                       add(a, 16);
  10%程度速度向上があった       jmp(".lp");
                    L(".headCmp");
                     }
                     jnc(".notFound");
                     if (isSandyBridge) {
                        lea(a,ptr[a+c-16]);
                     } else {
                         add(a, c); }
夏のプログラムシンポジウム2012                         23 / 33
ビットを数える
 簡潔データ構造
  サイズnの{0, 1}からなるビットベクトルvに対して
   rank(x) = #{ v[i] = 1 | 0 <= i < x }
   select(m) = min { i | rank(i) = m }
   を基本関数として圧縮検索などさまざまなロジックに利用
  (注)普通はrank()は0 <= i <= xの範囲で定義
  rankはまさにビットカウント関数
  ビューティフルコード10章
    「高速ビットカウントを求めて」の続き?
  ここでは32bit/64bitに対するビットカウント命令
    popcntの存在は前提




夏のプログラムシンポジウム2012                     24 / 33
ナイーブな実装
 岡野原さんの2006年CodeZine記事ベース
  256bitごとの累積値をuint32_t a[]に保存
  a[i] := rank(256 * i)
  そこからの64bitごとの差分累積値をuint8_t b[]に保存
  b[i] := rank(64 * i) – rank(64 * (i & ~3))
  必要なメモリは(32 + 8 * 4) / 256 = 1/4(bitあたり)
  rank(x) = a[i/256] + b[i/64]+ popcnt(v[i/64] & mask);
  ランダムアクセスするのでキャッシュミスが頻出
          .    .                 .   .           .         .         .          .
0 1 1 0          0 1   0 1 1 0         0 1 ... 0   1   0     1   1     1   1      0
          .    .                 .   .           .         .         .          .
              256bit                              64bit
      a[0]                   a[1]             b[x+0]   b[x+1]    b[x+2]        b[x+3]


夏のプログラムシンポジウム2012                                                               25 / 33
メモリを減らして高速化できる?
 キャッシュ効率の向上
  テーブルは一つにまとめる
 256bit単位ではなく512bit単位で集めてみる
  64bitで区切ると8個
  するとメモリ必要量は(32 + 32 + 8 * 8) / 512 = 1/4
  変わらない
  128bitで区切ると4個
  メモリ必要量は(32 + 8 * 4) / 512 = 1/8
  半分になる
  問題点
  256bitを超えるのでuint8_tな配列b[]の積算で
    オーバーフローしてしまう
     オンデマンドで総和を求める?
夏のプログラムシンポジウム2012                            26 / 33
4個未満のsum()
 最適化したいコード(0 <= n < 4)
  Xeonで35clk, i7で30lk程度であった(乱数生成込み)
int sum1(const uint8_t data[4], int n) {
    int sum = 0;
    for (int i = 0; i < n; i++) {
        sum += data[i];
    }
    return sum;
}



  XorShift128 rg;
  for (int j = 0; j < C; j++) {
     ret += sum1(data, rg.get() % 4);
  }

夏のプログラムシンポジウム2012                          27 / 33
ループ展開してみた
int sum2(const uint8_t data[4], int n) {
    int sum = 0;
    switch (n) {
    case 3: sum += data[2];
    case 2: sum += data[1];
    case 1: sum += data[0];
    }
    return sum;
}


 Xeon 35→26clk, i7 30→24clk
  Loop Stream Detector(LSD)




夏のプログラムシンポジウム2012                          28 / 33
psadbwを使ってみる
 psadbw X, Y
  MPEGなどのビデオコーデックにおいて
   二つのbyte単位のデータの差の絶対値の和を求める命令
  psadbw(X, Y) = sum [abs(X[i] – Y[i]) | i = 0..7]
  画像の一致度を求める
  Y = 0とするとXのbyte単位の和を求められる
  Xをマスクして足せばよい
   x0     x1     x2    x3    x4   x5   x6   x7
                       and
  0xff   0xff   0xff    0    0    0    0    0


  x0     x1     x2     0     0    0    0    0

夏のプログラムシンポジウム2012
                       x0 + x1 + x2                   29 / 33
実際のコード
int sum3(const uint8_t data[4], int n) {
  uint32_t x = *reinterpret_cast<const uint32_t*>(data);
  x &= (1U << (n * 8)) - 1;
  V128 v(x);
  v = psadbw(v, Zero());
  return movd(v);
}

 Xeon 35→26→10clk!
  i7     30→24→10clk!
  速くなった
  このコードはn < 8まで適用可能
  ちょっと改良すればn < 16まで可能



夏のプログラムシンポジウム2012                                          30 / 33
128bitマスク
 残りはマスクしてpopcntする部分
// 疑似コード
uint128_t mask128(int n) {
  return (uint128_t(1) << n) – 1; }

int get(uint12_t x, int n) {
  return popcnt_128(x & mask128(n)); }

  実際には64bit整数に分解して実行する
  n < 63 と n >= 64に場合分けする
uint64_t m0 = -1;
uint64_t m1 = 0;
if (!(n > 64)) m0 = mask;
if (n > 64) m1 = mask;
ret += popCount64(b0 & m0);
ret += popCount64(b1 & m1);

夏のプログラムシンポジウム2012                        31 / 33
分岐除去
 先程のコードはgcc4.6では条件分岐命令を生成する
  データがランダムなら確率50%で分岐予測が外れる
  cmovを使って実装
  条件が成立したときのみmovを行う命令
  if (n > 64)はcarryを変更しなければ一度だけでよい
  6clkぐらい速くなる
     メモリアクセスの影響が多いと見えにくくなる
or(m0, -1);
and(n, 64); // ZF = (n < 64) ? 1 : 0
cmovz(m0, mask); // m0 = (!(n > 64)) ? mask : -1
cmovz(mask, idx); // mask = (n > 64) ? mask : 0
and(m0, ptr [blk + rax * 8 + 0]);
and(mask, ptr [blk + rax * 8 + 8]);
popcnt(m0, m0);
popcnt(rax, mask);
夏のプログラムシンポジウム2012                                  32 / 33
select1のベンチマーク
 メモリを大量に使うところでは速い
  marisa-trieのbit-vectorよりも速い
  少ないところではオーバーヘッドがややある
  まだ改良の余地あり・あるいはnによって戦略を変える
                           rankの処理時間(clk)                           fast
  200

  150

  100

   50

    0
        128KiB 0.5MiB   2MiB   8MiB   32MiB 0.1GiB 0.5GiB   2GiB   4GiB
                               SBV1(org)   SBV2

夏のプログラムシンポジウム2012                                                         33 / 33

More Related Content

What's hot (20)

PDF
準同型暗号の実装とMontgomery, Karatsuba, FFT の性能
MITSUNARI Shigeo
 
PDF
From IA-32 to avx-512
MITSUNARI Shigeo
 
PDF
SSE4.2の文字列処理命令の紹介
MITSUNARI Shigeo
 
PDF
optimal Ate pairing
MITSUNARI Shigeo
 
PDF
HPC Phys-20201203
MITSUNARI Shigeo
 
PDF
LLVMで遊ぶ(整数圧縮とか、x86向けの自動ベクトル化とか)
Takeshi Yamamuro
 
PDF
汎用性と高速性を目指したペアリング暗号ライブラリ mcl
MITSUNARI Shigeo
 
PDF
ElGamal型暗号文に対する任意関数演算・再暗号化の二者間秘密計算プロトコルとその応用
MITSUNARI Shigeo
 
PDF
Intro to SVE 富岳のA64FXを触ってみた
MITSUNARI Shigeo
 
PDF
emcjp Item 42
MITSUNARI Shigeo
 
KEY
関東GPGPU勉強会 LLVM meets GPU
Takuro Iizuka
 
PDF
WASM(WebAssembly)入門 ペアリング演算やってみた
MITSUNARI Shigeo
 
PDF
Intel AVX-512/富岳SVE用SIMDコード生成ライブラリsimdgen
MITSUNARI Shigeo
 
PDF
Xbyakの紹介とその周辺
MITSUNARI Shigeo
 
PDF
きつねさんでもわかるLlvm読書会 第2回
Tomoya Kawanishi
 
PPTX
高速な暗号実装のためにしてきたこと
MITSUNARI Shigeo
 
PDF
研究動向から考えるx86/x64最適化手法
Takeshi Yamamuro
 
PPTX
AVX-512(フォーマット)詳解
MITSUNARI Shigeo
 
PDF
GPUが100倍速いという神話をぶち殺せたらいいな ver.2013
Ryo Sakamoto
 
PDF
Effective Modern C++ 読書会 Item 35
Keisuke Fukuda
 
準同型暗号の実装とMontgomery, Karatsuba, FFT の性能
MITSUNARI Shigeo
 
From IA-32 to avx-512
MITSUNARI Shigeo
 
SSE4.2の文字列処理命令の紹介
MITSUNARI Shigeo
 
optimal Ate pairing
MITSUNARI Shigeo
 
HPC Phys-20201203
MITSUNARI Shigeo
 
LLVMで遊ぶ(整数圧縮とか、x86向けの自動ベクトル化とか)
Takeshi Yamamuro
 
汎用性と高速性を目指したペアリング暗号ライブラリ mcl
MITSUNARI Shigeo
 
ElGamal型暗号文に対する任意関数演算・再暗号化の二者間秘密計算プロトコルとその応用
MITSUNARI Shigeo
 
Intro to SVE 富岳のA64FXを触ってみた
MITSUNARI Shigeo
 
emcjp Item 42
MITSUNARI Shigeo
 
関東GPGPU勉強会 LLVM meets GPU
Takuro Iizuka
 
WASM(WebAssembly)入門 ペアリング演算やってみた
MITSUNARI Shigeo
 
Intel AVX-512/富岳SVE用SIMDコード生成ライブラリsimdgen
MITSUNARI Shigeo
 
Xbyakの紹介とその周辺
MITSUNARI Shigeo
 
きつねさんでもわかるLlvm読書会 第2回
Tomoya Kawanishi
 
高速な暗号実装のためにしてきたこと
MITSUNARI Shigeo
 
研究動向から考えるx86/x64最適化手法
Takeshi Yamamuro
 
AVX-512(フォーマット)詳解
MITSUNARI Shigeo
 
GPUが100倍速いという神話をぶち殺せたらいいな ver.2013
Ryo Sakamoto
 
Effective Modern C++ 読書会 Item 35
Keisuke Fukuda
 

Viewers also liked (8)

PPTX
Jenkins User Conference 東京 2015
Kohsuke Kawaguchi
 
PDF
Gitのよく使うコマンド
YUKI Kaoru
 
KEY
バージョン管理のワークフロー
add20
 
PDF
もしWordPressユーザーがGitを使ったら 〜WordPressテーマを共同編集しよう〜
Takashi Uemura
 
PDF
デザイナのためのGit入門
dsuke Takaoka
 
KEY
一人でもはじめるGitでバージョン管理
Takafumi Yoshida
 
PDF
こわくない Git
Kota Saito
 
PDF
いつやるの?Git入門
Masakazu Matsushita
 
Jenkins User Conference 東京 2015
Kohsuke Kawaguchi
 
Gitのよく使うコマンド
YUKI Kaoru
 
バージョン管理のワークフロー
add20
 
もしWordPressユーザーがGitを使ったら 〜WordPressテーマを共同編集しよう〜
Takashi Uemura
 
デザイナのためのGit入門
dsuke Takaoka
 
一人でもはじめるGitでバージョン管理
Takafumi Yoshida
 
こわくない Git
Kota Saito
 
いつやるの?Git入門
Masakazu Matsushita
 
Ad

Similar to Prosym2012 (20)

PDF
PFI Seminar 2010/02/18
Preferred Networks
 
PDF
Rubysapporo Stringsearch
Akio Ishida
 
PDF
V6 unix in okinawa
magoroku Yamamoto
 
PDF
Boost Tour 1.50.0 All
Akira Takahashi
 
PDF
Boost tour 1_40_0
Akira Takahashi
 
PDF
boost tour 1.48.0 all
Akira Takahashi
 
PDF
d-kami x86-1
Daisuke Kamikawa
 
PDF
V6 unix vol.2 in okinawa
magoroku Yamamoto
 
PDF
KLab勉強会#6 発表資料
Suguru Oho
 
PDF
プログラムを高速化する話
京大 マイコンクラブ
 
PDF
“Symbolic bounds analysis of pointers, array indices, and accessed memory reg...
Masahiro Sakai
 
ODP
Rubyのソースコードを読んでみよう(入門編)
baban ba-n
 
PDF
V6でJIT・部分適用・継続
7shi
 
PDF
プログラミング作法
Kota Uchida
 
PDF
文字列曖昧検索によるマルウェアバイナリ解析
Preferred Networks
 
PDF
Tora pointer3
MARISHI
 
PDF
2011.12.10 関数型都市忘年会 発表資料「最近書いた、関数型言語と関連する?C++プログラムの紹介」
Hiro H.
 
PDF
あまぁいRcpp生活
Masaki Tsuda
 
PPT
Ilerpg Study 004
Yoshiki Ushida
 
PDF
x64 のスカラー,SIMD 演算性能を測ってみた @ C++ MIX #10
Muneyoshi Suzuki
 
PFI Seminar 2010/02/18
Preferred Networks
 
Rubysapporo Stringsearch
Akio Ishida
 
V6 unix in okinawa
magoroku Yamamoto
 
Boost Tour 1.50.0 All
Akira Takahashi
 
Boost tour 1_40_0
Akira Takahashi
 
boost tour 1.48.0 all
Akira Takahashi
 
d-kami x86-1
Daisuke Kamikawa
 
V6 unix vol.2 in okinawa
magoroku Yamamoto
 
KLab勉強会#6 発表資料
Suguru Oho
 
プログラムを高速化する話
京大 マイコンクラブ
 
“Symbolic bounds analysis of pointers, array indices, and accessed memory reg...
Masahiro Sakai
 
Rubyのソースコードを読んでみよう(入門編)
baban ba-n
 
V6でJIT・部分適用・継続
7shi
 
プログラミング作法
Kota Uchida
 
文字列曖昧検索によるマルウェアバイナリ解析
Preferred Networks
 
Tora pointer3
MARISHI
 
2011.12.10 関数型都市忘年会 発表資料「最近書いた、関数型言語と関連する?C++プログラムの紹介」
Hiro H.
 
あまぁいRcpp生活
Masaki Tsuda
 
Ilerpg Study 004
Yoshiki Ushida
 
x64 のスカラー,SIMD 演算性能を測ってみた @ C++ MIX #10
Muneyoshi Suzuki
 
Ad

More from MITSUNARI Shigeo (20)

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

Recently uploaded (9)

PPTX
baserCMS『カスタムコンテンツ』徹底活用術〜あなただけの管理画面を自由自在に〜
Ryuji Egashira
 
PDF
20250726_Devinで変えるエンプラシステム開発の未来
Masaki Yamakawa
 
PDF
LoRaWAN ウェザーステーションキット v3 -WSC3-L 日本語ユーザーマニュアル
CRI Japan, Inc.
 
PDF
VMUG Japan book vsan 20250515 CPU/Memory vSAN
Kazuhiro Sota
 
PDF
第三世代 ウェザーステーションキット v3 ー WSC3-L 日本語カタログ
CRI Japan, Inc.
 
PDF
MahiroYoshida_セリフに着目したキャラクタロール推定に関する基礎検討_sigcc12th2025
Matsushita Laboratory
 
PPTX
2025_7_25_吉祥寺_設計ナイト_ADR運用におけるデータ利活用の考え方.pptx
ssuserfcafd1
 
PDF
TaketoFujikawa_ComicComputing12th_inKumamoto
Matsushita Laboratory
 
PDF
【学会聴講報告】CVPR2025からみるVision最先端トレンド / CVPR2025 report
Sony - Neural Network Libraries
 
baserCMS『カスタムコンテンツ』徹底活用術〜あなただけの管理画面を自由自在に〜
Ryuji Egashira
 
20250726_Devinで変えるエンプラシステム開発の未来
Masaki Yamakawa
 
LoRaWAN ウェザーステーションキット v3 -WSC3-L 日本語ユーザーマニュアル
CRI Japan, Inc.
 
VMUG Japan book vsan 20250515 CPU/Memory vSAN
Kazuhiro Sota
 
第三世代 ウェザーステーションキット v3 ー WSC3-L 日本語カタログ
CRI Japan, Inc.
 
MahiroYoshida_セリフに着目したキャラクタロール推定に関する基礎検討_sigcc12th2025
Matsushita Laboratory
 
2025_7_25_吉祥寺_設計ナイト_ADR運用におけるデータ利活用の考え方.pptx
ssuserfcafd1
 
TaketoFujikawa_ComicComputing12th_inKumamoto
Matsushita Laboratory
 
【学会聴講報告】CVPR2025からみるVision最先端トレンド / CVPR2025 report
Sony - Neural Network Libraries
 

Prosym2012

  • 1. 高機能アセンブラによるx86/x64 CPU向 け高速化テクニック Cybozu Labs 2012/8/24 光成滋生
  • 2. 目次  x86/x64アセンブラの復習  C++用のx86/x64アセンブラXbyakの紹介 サンプル  文字列検索  ビットを数える  自己紹介  x86/x64最適化勉強会主催  https://github.com/herumi/opti/  http://www.slideshare.net/herumi/ 夏のプログラムシンポジウム2012 2 / 33
  • 3. x86/x64アセンブラの復習(1/2)  汎用レジスタは15個(64bit時)  rax, rbx, ..., r8, ..., r15 ; 64bitレジスタ  rsp : スタックレジスタ  基本的に2オペランド  <op> <dst> <src> // dst ← op(dst, src); dstは破壊される  アドレッシングは比較的高機能 ptr [<reg1> + <reg2> * (0|1|2|4|8) + <即値>] 例) mov rax, rcx // rax = rcx; add ecx, 4 // ecx += 4; sub r8,[rax+ebx*4+12]//r8 -= *(int64_t)(rax+ebx*4+12); 夏のプログラムシンポジウム2012 3 / 33
  • 4. x86/x64アセンブラの復習(2/2)  SIMDレジスタは16個(64bit時)  xmm0, ..., xmm15 ; 128bitレジスタ  ymm0, ..., ymm15 ; 256bitレジスタ  データ型  char x 16, int x 4, float x 8, double x 4, etc.  演算の種類  四則演算,ビット演算,特殊演算,etc. 3オペランドタイプもある 例) movaps xmm0,[eax] //xmm0にfloat変数4個が代入される vaddpd ymm0,ymm3,ymm2 //ymm0 = ymm3 + ymm2(double x 4) pand xmm2, xmm4 //xmm2 &= xmm4 夏のプログラムシンポジウム2012 4 / 33
  • 5. Xbyakの特長  C++ヘッダのみで記述  外部ライブラリのビルドやリンクが不要 対応コンパイラ : Visual Studio/gcc/clang 対応OS : Windows/Linux/Mac  実行時コード生成  Intel MASM形式に似せたDSLを提供  できるだけ自然に記述できるようにした アドレッシングは設計時に一番悩んだ部分  かつ一番気に入ってる部分 mov(eax, 5); // mov eax, 5 add(rcx, byte[eax+ecx*4–5]);//add rcx,byte[eax+ecx*4-5] jmp("lp"); // jmp lp 夏のプログラムシンポジウム2012 5 / 33
  • 6. 簡単なサンプル(1/3)  整数nが与えられたときにnを足す関数を生成  ヘッダをincludeしてCodeGeneratorを継承 #include <xbyak.h> struct Code : Xbyak::CodeGenerator { explicit Code(int n) { mov(eax, ptr [esp + 4]); // 32bit OS add(eax, n); // lea(rax, ptr [rcx + n]); // 64bit Windows // lea(eax, ptr [edi + n]); // 64bit Linux/Mac ret(); } }; 夏のプログラムシンポジウム2012 6 / 33
  • 7. 簡単なサンプル(2/3)  インスタンスを生成して実行 int main(int argc, char *argv[]) { const int n = argc == 1 ? 5 : atoi(argv[1]); Code c(n); auto f = (int (*)(int))c.getCode(); for (int i = 0; i < 3; i++) { printf("%d + %d = %dn", i, n, f(i)); } } % ./a.out 9 0 + 9 = 9 1 + 9 = 10 2 + 9 = 11 夏のプログラムシンポジウム2012 7 / 33
  • 8. 簡単なサンプル(3/3)  インスタンスを生成して実行  引数が9のときの関数fの中身をデバッガで確認 32bit Windowsで見てみた mov eax,dword ptr [esp+4] add eax,9 // ここが即値 ret  引数が3のときの関数fの中身をデバッガで確認 64bit Linuxで見てみた 0x0000000000607000 in ?? () 1: x/i $pc 0x607000: lea eax,[edi+0x3] // ここが即値 夏のプログラムシンポジウム2012 8 / 33
  • 9. 応用例  ビューティフルコード8章  画像処理のためのその場コード生成  画像処理では関数内では固定のパラメータが多い templateなどを組み合わせるとバイナリサイズが肥大化 夏のプログラムシンポジウム2012 9 / 33
  • 10. 画像処理のためのその場コード生成  ビットマップD, Sを合成変換する関数  変換種類を示すopがループの最内部分にあるため通常の実装 では遅すぎる 1985年のWindowsではそのコードを生成するミニコンパイラ を含んでいたらしい for (int y = 0; y < cy; y++) { for (int x = 0; x < cx; x++) { switch (op) { case 0x00: D[y][x] = 0; break; ... case 0x60: D[y][x] = (D[y][x] ^ S[y][x]) & P[y][x]; ... } } } 夏のプログラムシンポジウム2012 10 / 33
  • 11. Xbyakによるその場コード生成  メリット Code(const Rect& rect, int op){ // generate prolog  C++の文法でasmの制御構造を L(".lp_y"); 記述できる mov(rcx, rect.width); L(".lp_x"); 極めて直感的に扱える switch (op) { アセンブラ独自の疑似命令を .. 覚える必要がない case 0x60: mov(eax,ptr[ptrD+rbx]);  Cの構造体との連携が xor(eax,ptr[ptrS+rbx]); シームレス and(eax,ptr[ptrP+rbx]); 構造体メンバのオフセット break; 取得は外部ツールでは困難 } mov(ptr[ptrD + rbx], eax); Xbyakなら<stddef.h>の add(rbx, 4); offsetof(type, member) sub(rcx, 1); をそのまま利用可能 jnz(".lp_x"); ... 夏のプログラムシンポジウム2012 11 / 33
  • 12. FFTのバタフライ演算(1/2)  ビット反転とデータ移動 void swap(double*a,int k1,int j1){ __m128d x = _mm_load_pd(a + j1);  入力は次数nとデータa __m128d y = _mm_load_pd(a + k1); ipはビット反転用work __mm_store_pd(a + j1, y); __mm_store_pd(a + k1, x);  nは可変だが,128とか } 256とかが多いケース void bitrv(int n,int *ip,double *a)  通常は事前に専用コード { int j, j1, k, k1, l, m, m2; を用意して分岐する l = n; m = 1; ツールでCのコードを生成 ... するなど m2 = 2 * m; if ((m << 3) == l) { for (k = 0; k < m; k++) { for (j = 0; j < k; j++) { j1 = 2 * j + ip[k]; k1 = 2 * k + ip[j]; swap(a, j1, k1); 夏のプログラムシンポジウム2012 12 / 33
  • 13. FFTのバタフライ演算(2/2)  修正箇所はわずか struct Code : Xbyak::CodeGenerator {  swap()をコード生成 void swap(const Reg32e& a , int k1, int j1){ するように変更 movapd(xm0, ptr [a + j1 * 8]);  関数のプロローグと movapd(xm1, ptr [a + k1 * 8]); エピローグを作る movapd(ptr [a + j1 * 8], xm1); movapd(ptr [a + k1 * 8], xm0);  生成コード例 } 定数とループが全て void gen_bitrv2(int n, int *ip){ 展開される const Reg64& a = rdi; int j, j1, k, k1, l, m, m2; movapd xm0,ptr [eax+10h] ... movapd xm1,ptr [eax+100h] j1 = 2 * j + ip[k]; movapd ptr [eax+10h],xm1 k1 = 2 * k + ip[j]; movapd ptr [eax+100h],xm0 swap(a, j1, k1); movapd xm0,ptr [eax+50h] ... movapd xm1,ptr [eax+140h] ... 夏のプログラムシンポジウム2012 13 / 33
  • 14. SSE4.1の文字列探索命令の紹介  pcmpestri, pcmpistriなど  strlenやstrstrなどを高速に実装するための命令群  複雑なパラメータを持つCISCの権化の様な命令  文字列処理の単位char or short(2byte:UTF16)の選択  符号付き・符号無しの選択  文字列の比較方法の選択 完全マッチ,文字の範囲指定,部分文字列など  入力文字列の設定 0ターミネート文字列 or [begin, end)による文字列  CF, ZF, SF, OFの各フラグに結果の様々な情報  詳細  http://www.slideshare.net/herumi/x86opti3 夏のプログラムシンポジウム2012 14 / 33
  • 15. strstrを作ってみる  16byteずつ文字列を探索する  入力パラメータ a : 入力テキスト(text)ポインタが格納されたレジスタ xm0 : 検索文字(の先頭最大16byte) 12 : 文字列マッチをuint8_tの部分文字マッチさせる即値  出力フラグ CF : 文字列を見つける前にtextが終われば1 ZF : aから16byte内に文字列がなければ0 movdqu(xm0, ptr [key]); // xm0 = *key L(".lp"); pcmpistri(xm0, ptr [a], 12); lea(a, ptr [a + 16]); ja(".lp"); jnc(".notFound"); 夏のプログラムシンポジウム2012 15 / 33
  • 16. strstrのコア部分(続き)  keyの先頭文字が一致する部分を検出した  先程のループを抜けたときはcにマッチしたオフセットが入っ ているのでそれだけ進める(add a, c)  その場所からkeyの最後まで一致しているかを確認 OF == 0なら一致しなかった SF == 1なら見つかった add(a, c); mov(save_a, a); // save a mov(save_key, key); // save key L(".tailCmp"); movdqu(xm1, ptr [save_key]); pcmpistri(xm1, ptr [save_a], 12); jno(".next"); // if (OF == 0) goto .next js(".found"); // if (SF == 1) goto .found add(save_a, 16); add(save_key, 16); jmp(".tailCmp"); 夏のプログラムシンポジウム2012 16 / 33
  • 17. ベンチマーク  対象CPU, コンパイラ  Xeon X5650 + gcc 4.6.3  対象コード  gccのstrstr(これもSSE4.1を利用している)  boost 1.51のalgorithm::boyer_moore(BM法)  Quick Search(改良版BMアルゴリズム)  my_strstr  検索方法  テキスト:130MBのUTF8な日本語を多く含むもの 指定されたkey文字列がいくつあるかを探す byte単位あたりにかかったCPU clock数を表示する 夏のプログラムシンポジウム2012 17 / 33
  • 18. strstrのベンチマーク  Xeon X5650 + gcc 4.6.3 8 string::find 7 boost::bm fast cycle/Byte to find 6 quick search 5 strstr(gcc) 4 my_strstr 3 2 1 0 static_ass a ab 1234 これは 00...0 AB...Z ert string::find 3.37 3.04 3.23 7.39 2.95 3.27 2.8 boost::bm 6.76 6.27 3.23 1.74 1.07 0.93 0.56 quick search 4.7 3.12 1.99 3.4 0.85 0.7 0.54 strstr(gcc) 1.64 1.13 1.12 1.15 1.11 1.18 0.46 my_strstr 0.6 0.3 0.29 0.8 0.28 0.3 0.27 夏のプログラムシンポジウム2012 18 / 33
  • 19. 考察  ABC....Zの様なBM法やQuick Searchアルゴリズムにと って有利な文字列すらstrstrより遅い  BM法やQuick Searchはテーブルを引いてオフセットを足すた めパイプラインに悪影響がある  通常の文字列検索では出番がない? // Quick Searchの検索部分 const char *find(const char *begin, const char *end) const { while (begin <= end - len_) { for (size_t i = 0; i < len_; i++) { if (str_[i] != begin[i]) goto NEXT; } return begin; NEXT: begin += tbl_[static_cast<unsigned char>(begin[len_])]; } return end; } 夏のプログラムシンポジウム2012 19 / 33
  • 20. strcasestrの実装  keyの大文字小文字を区別しないstrstr  コードの簡略さのためにkeyは事前に小文字化しておく  大文字小文字を区別しないマッチ方法  textを小文字にしてマッチさせる(先程のコードに委譲) movdqu(xm0, ptr [key]); // xm0 = *key L(".lp"); if (caseInsensitive) { movdqu(xm1, ptr [a]); toLower(xm1, Am1, Zp1, amA, t0, t1);//小文字化するコード生成関数 pcmpistri(xm0, xm1, 12); } else { pcmpistri(xm0, ptr [a], 12); } lea(a, ptr [a + 16]); ja(".lp"); // if (CF == 0 and ZF = 0) goto .lp jnc(".notFound"); 夏のプログラムシンポジウム2012 20 / 33
  • 21. toLower(1/2)  大文字を小文字にする  'A' <= c && c <= 'Z'なら c += 'a' – 'Z'  分岐無しで16byteずつまとめてやりたい  pcmpgtb x, y  x ← x > y ? 0xff : 0;をbyte単位で行う関数  if ('A' <= c && c <= 'Z') c += 'a' – 'Z';を書き換える ('A' <= c && c <= 'Z') ? ('a' – 'Z') : 0; = ((c > 'A'–1) ? 0xff : 0) & (('Z'+1 > c) ? 0xff : 0) & ('a'–'Z'); = pcmpgtb(c, 'A'-1) & pcmpgtb('Z'+1, c) & 'a'-'Z'; 夏のプログラムシンポジウム2012 21 / 33
  • 22. toLower(2/2)  実際のコード片 /* toLower in x Am1 : 'A' – 1, Zp1 : 'Z' + 1, amA : 'a' - 'A' t0, t1 : temporary register */ void toLower(const Xmm& x, const Xmm& Am1, const Xmm& Zp1 , const Xmm& amA , const Xmm& t0, const Xmm& t1) { movdqa(t0, x); pcmpgtb(t0, Am1); // -1 if c > 'A' - 1 movdqa(t1, Zp1); pcmpgtb(t1, x); // -1 if 'Z' + 1 > c pand(t0, t1); // -1 if [A-Z] pand(t0, amA); // 0x20 if c in [A-Z] paddb(x, t0); // [A-Z] -> [a-z] } 夏のプログラムシンポジウム2012 22 / 33
  • 23. CPU判別によるディスパッチ  コア部分再度(詳細) pcmpistri(xm0,ptr[a],12);  実験によるとCPUの世代で if (isSandyBridge) { コードの書き方で速度が違う lea(a, ptr [a + 16]); (lea vs add) ja(".lp"); } else {  実行時にCPU判別することで jbe(".headCmp"); 適切なコード生成を行う add(a, 16);  10%程度速度向上があった jmp(".lp"); L(".headCmp"); } jnc(".notFound"); if (isSandyBridge) { lea(a,ptr[a+c-16]); } else { add(a, c); } 夏のプログラムシンポジウム2012 23 / 33
  • 24. ビットを数える  簡潔データ構造  サイズnの{0, 1}からなるビットベクトルvに対して rank(x) = #{ v[i] = 1 | 0 <= i < x } select(m) = min { i | rank(i) = m } を基本関数として圧縮検索などさまざまなロジックに利用 (注)普通はrank()は0 <= i <= xの範囲で定義  rankはまさにビットカウント関数 ビューティフルコード10章 「高速ビットカウントを求めて」の続き? ここでは32bit/64bitに対するビットカウント命令 popcntの存在は前提 夏のプログラムシンポジウム2012 24 / 33
  • 25. ナイーブな実装  岡野原さんの2006年CodeZine記事ベース  256bitごとの累積値をuint32_t a[]に保存 a[i] := rank(256 * i)  そこからの64bitごとの差分累積値をuint8_t b[]に保存 b[i] := rank(64 * i) – rank(64 * (i & ~3))  必要なメモリは(32 + 8 * 4) / 256 = 1/4(bitあたり)  rank(x) = a[i/256] + b[i/64]+ popcnt(v[i/64] & mask); ランダムアクセスするのでキャッシュミスが頻出 . . . . . . . . 0 1 1 0 0 1 0 1 1 0 0 1 ... 0 1 0 1 1 1 1 0 . . . . . . . . 256bit 64bit a[0] a[1] b[x+0] b[x+1] b[x+2] b[x+3] 夏のプログラムシンポジウム2012 25 / 33
  • 26. メモリを減らして高速化できる?  キャッシュ効率の向上  テーブルは一つにまとめる  256bit単位ではなく512bit単位で集めてみる  64bitで区切ると8個  するとメモリ必要量は(32 + 32 + 8 * 8) / 512 = 1/4 変わらない  128bitで区切ると4個  メモリ必要量は(32 + 8 * 4) / 512 = 1/8 半分になる  問題点 256bitを超えるのでuint8_tな配列b[]の積算で オーバーフローしてしまう  オンデマンドで総和を求める? 夏のプログラムシンポジウム2012 26 / 33
  • 27. 4個未満のsum()  最適化したいコード(0 <= n < 4)  Xeonで35clk, i7で30lk程度であった(乱数生成込み) int sum1(const uint8_t data[4], int n) { int sum = 0; for (int i = 0; i < n; i++) { sum += data[i]; } return sum; } XorShift128 rg; for (int j = 0; j < C; j++) { ret += sum1(data, rg.get() % 4); } 夏のプログラムシンポジウム2012 27 / 33
  • 28. ループ展開してみた int sum2(const uint8_t data[4], int n) { int sum = 0; switch (n) { case 3: sum += data[2]; case 2: sum += data[1]; case 1: sum += data[0]; } return sum; }  Xeon 35→26clk, i7 30→24clk  Loop Stream Detector(LSD) 夏のプログラムシンポジウム2012 28 / 33
  • 29. psadbwを使ってみる  psadbw X, Y  MPEGなどのビデオコーデックにおいて 二つのbyte単位のデータの差の絶対値の和を求める命令  psadbw(X, Y) = sum [abs(X[i] – Y[i]) | i = 0..7] 画像の一致度を求める  Y = 0とするとXのbyte単位の和を求められる  Xをマスクして足せばよい x0 x1 x2 x3 x4 x5 x6 x7 and 0xff 0xff 0xff 0 0 0 0 0 x0 x1 x2 0 0 0 0 0 夏のプログラムシンポジウム2012 x0 + x1 + x2 29 / 33
  • 30. 実際のコード int sum3(const uint8_t data[4], int n) { uint32_t x = *reinterpret_cast<const uint32_t*>(data); x &= (1U << (n * 8)) - 1; V128 v(x); v = psadbw(v, Zero()); return movd(v); }  Xeon 35→26→10clk! i7 30→24→10clk!  速くなった  このコードはn < 8まで適用可能 ちょっと改良すればn < 16まで可能 夏のプログラムシンポジウム2012 30 / 33
  • 31. 128bitマスク  残りはマスクしてpopcntする部分 // 疑似コード uint128_t mask128(int n) { return (uint128_t(1) << n) – 1; } int get(uint12_t x, int n) { return popcnt_128(x & mask128(n)); }  実際には64bit整数に分解して実行する n < 63 と n >= 64に場合分けする uint64_t m0 = -1; uint64_t m1 = 0; if (!(n > 64)) m0 = mask; if (n > 64) m1 = mask; ret += popCount64(b0 & m0); ret += popCount64(b1 & m1); 夏のプログラムシンポジウム2012 31 / 33
  • 32. 分岐除去  先程のコードはgcc4.6では条件分岐命令を生成する  データがランダムなら確率50%で分岐予測が外れる  cmovを使って実装 条件が成立したときのみmovを行う命令 if (n > 64)はcarryを変更しなければ一度だけでよい 6clkぐらい速くなる  メモリアクセスの影響が多いと見えにくくなる or(m0, -1); and(n, 64); // ZF = (n < 64) ? 1 : 0 cmovz(m0, mask); // m0 = (!(n > 64)) ? mask : -1 cmovz(mask, idx); // mask = (n > 64) ? mask : 0 and(m0, ptr [blk + rax * 8 + 0]); and(mask, ptr [blk + rax * 8 + 8]); popcnt(m0, m0); popcnt(rax, mask); 夏のプログラムシンポジウム2012 32 / 33
  • 33. select1のベンチマーク  メモリを大量に使うところでは速い  marisa-trieのbit-vectorよりも速い  少ないところではオーバーヘッドがややある まだ改良の余地あり・あるいはnによって戦略を変える rankの処理時間(clk) fast 200 150 100 50 0 128KiB 0.5MiB 2MiB 8MiB 32MiB 0.1GiB 0.5GiB 2GiB 4GiB SBV1(org) SBV2 夏のプログラムシンポジウム2012 33 / 33