生命保険の動向2013年版(PDF) - seiho.or.jp · はじめに 『生命保険の動向』は、生命保険協会加盟の生命保険会社を対象に、生 命保険事業の業績の中から主な
SSE4.2の文字列処理命令の紹介
-
Upload
mitsunari-shigeo -
Category
Technology
-
view
7.071 -
download
3
description
Transcript of SSE4.2の文字列処理命令の紹介
![Page 1: SSE4.2の文字列処理命令の紹介](https://reader033.fdocument.pub/reader033/viewer/2022050817/55660e2dd8b42aa6628b5376/html5/thumbnails/1.jpg)
SSE4.2の文字列処理命令の紹介 SSE4.2の文字列処理命令の紹介
Cybozu Labs
2011/8/6 光成滋生(8/23加筆修正p3, p.25)
x86/x64最適化勉強会#1
2011/8/6 /41 1
![Page 2: SSE4.2の文字列処理命令の紹介](https://reader033.fdocument.pub/reader033/viewer/2022050817/55660e2dd8b42aa6628b5376/html5/thumbnails/2.jpg)
内容 内容
SIMD向き/不向き
SSE2によるstrlen
SSE4.2の文字列処理命令
SSE4.2によるstrlen
intrinsic命令
単語を数える
改良
まとめ
/41 2 2011/8/6
![Page 3: SSE4.2の文字列処理命令の紹介](https://reader033.fdocument.pub/reader033/viewer/2022050817/55660e2dd8b42aa6628b5376/html5/thumbnails/3.jpg)
説明とコード 説明とコード
Intelプロセッサ最適化マニュアルを読もう
http://homepage1.nifty.com/herumi/prog/intel-opt.html
コード片
https://github.com/herumi/opti/
アライメントとページ境界に関する補足(必読)
http://homepage1.nifty.com/herumi/diary/1108.html#8
/41 3 2011/8/6
![Page 4: SSE4.2の文字列処理命令の紹介](https://reader033.fdocument.pub/reader033/viewer/2022050817/55660e2dd8b42aa6628b5376/html5/thumbnails/4.jpg)
SIMD向き/不向き SIMD向き/不向き
SIMDは4個(or 8/16)個のデータを同時に同じように処理するためのもの
SIMD向き
先程の最大値を求める場合,4個ずつやってもよかった
複数個同時に加算,掛け算,etc.
これらの処理は概ね得意
SIMD向きでないもの
本質的に分岐が多いもの
一つ前の状態に依存するもの
/41 4 2011/8/6
![Page 5: SSE4.2の文字列処理命令の紹介](https://reader033.fdocument.pub/reader033/viewer/2022050817/55660e2dd8b42aa6628b5376/html5/thumbnails/5.jpg)
SSE2によるstrlen SSE2によるstrlen
素朴なstrlen
SIMDを使うアイデア
16byteずつデータを取得してbyte単位で0と比較する
0があれば0xff, なければ0をbyte単位に取得できる
上記データの最上位ビット(MSB)をかき集める
下から数えて初めて1になった場所があればそこが'¥0'
/41 5
size_t strlenC(const char *p) { size_t len = 0; while (p[len]) len++; return len; }
2011/8/6
![Page 6: SSE4.2の文字列処理命令の紹介](https://reader033.fdocument.pub/reader033/viewer/2022050817/55660e2dd8b42aa6628b5376/html5/thumbnails/6.jpg)
xm0に文字列, xm1に0を入れておいてbyte比較
pcmpeqb xm0, xm1
xm0のMSBをかき集める
pmovmskb eax, xm0
1bitずつ16個合計16bitのデータになる
eax = 0b00100000
eaxが0で無ければ下から初めて1になる場所を探す
bsf eax, eax ; eax = 5となる
SIMDを使うアイデア SIMDを使うアイデア
xm0 +0 +1 +2 +3 +4 +5 +6 +7
xm0 h e l l o '¥0' ? ?
xm1 0 0 0 0 0 0 0 0
xm0 0 0 0 0 0 0xff 0 0
/41 6 2011/8/6
![Page 7: SSE4.2の文字列処理命令の紹介](https://reader033.fdocument.pub/reader033/viewer/2022050817/55660e2dd8b42aa6628b5376/html5/thumbnails/7.jpg)
実装 実装
https://github.com/herumi/opti/のstrlen_sse2.cpp
実際には16byteアライメントされていないと扱いにくいためループ開始前に端数処理がある
当時3年前(gcc 4.3やVC9に対して)は速かった
最近のgcc(4.4以降?)は同様の手法やこれから述べるSSE4.2の命令が用いられている
ただしgcc 4.6でもstrlenCがそういうコードになるわけではない
あくまでもライブラリ関数で使われているだけ
まとめ
アライメント処理 / byte単位での照合 / ビットスキャン
/41 7 2011/8/6
![Page 8: SSE4.2の文字列処理命令の紹介](https://reader033.fdocument.pub/reader033/viewer/2022050817/55660e2dd8b42aa6628b5376/html5/thumbnails/8.jpg)
SSE4.2の文字列処理命令 SSE4.2の文字列処理命令
超複雑
今述べたアライメントの処理(不要になった), byte単位での照合(より高機能), ビットスキャンを1命令で行う
pcmpXstrY xm0, xm1/mem, imm8
imm8でさまざまなモードを指定する
memは16byte alignmentされていなくてもよい
/41 8
0終端による文字列 edx/eaxによる長さ指定文字列
処理の結果をスキャンしてecxに出力
pcmpistri pcmpestri
処理の結果をそのままxm0に出力
pcmpistrm pcmpestrm
2011/8/6
![Page 9: SSE4.2の文字列処理命令の紹介](https://reader033.fdocument.pub/reader033/viewer/2022050817/55660e2dd8b42aa6628b5376/html5/thumbnails/9.jpg)
長さ指定モード 長さ指定モード
pcmpestri, pcmpestrmは明示的な長さを指定する
pcmpestrY xm0, xm1, imm8
eaxはxm0の文字列の長さ
edxはxm1の文字列の長さ
/41 9 2011/8/6
![Page 10: SSE4.2の文字列処理命令の紹介](https://reader033.fdocument.pub/reader033/viewer/2022050817/55660e2dd8b42aa6628b5376/html5/thumbnails/10.jpg)
imm8の内容 imm8の内容
imm8[0](最下位ビット)
0なら入力データをbyte単位とみなす 1ならword(2byte)単位と見なして処理
imm8[1]
0なら符号無しデータ 1なら符号ありデータとして処理
imm8[3:2]
文字列の比較方法を選択する
00b(equal array) マッチする最初の文字を探す
01b(ranges) 範囲内から文字列を探す
10b(equal each) 文字列比較をする
11b(equal ordered) 部分文字列比較をする /41 10 2011/8/6
![Page 11: SSE4.2の文字列処理命令の紹介](https://reader033.fdocument.pub/reader033/viewer/2022050817/55660e2dd8b42aa6628b5376/html5/thumbnails/11.jpg)
equal anyの疑似コード equal anyの疑似コード
set : 検索したい文字集合
text : 検索対象のテキスト
set = "abc", text = "xaybzc";
IntRes1[0] = setのどれもtext[0]にマッチしないのでfalse
IntRes1[1] = setのaがtext[1]にマッチするのでtrue
...
/41 11
for (int j = 0; j < len; j++) { int tmp = 0; for (int i = 0; i < len; i++) { tmp |= text[j] == set[i]; } IntRes1[j] = tmp; }
2011/8/6
![Page 12: SSE4.2の文字列処理命令の紹介](https://reader033.fdocument.pub/reader033/viewer/2022050817/55660e2dd8b42aa6628b5376/html5/thumbnails/12.jpg)
rangesの疑似コード rangesの疑似コード
str:[2*i+0] : 文字列範囲の始め
str[2*i+1] : 文字列範囲の終わり
str = "az09", text = "0Abc";
IntRes1[0] = text[0]は[0-9]にあるのでtrue
IntRes1[1] = text[1]はどこにも入らないのでfalse
IntRes1[2] = text[2]は[a-z]にあるのでtrue
/41 12
for (int j = 0; j < len; j++) { int tmp = 0; for (int i = 0; i < len; i += 2) { tmp |= (str[i] <= text[j]) && (text[j] <= str[i + 1]); } IntRes1[j] = tmp; }
2011/8/6
![Page 13: SSE4.2の文字列処理命令の紹介](https://reader033.fdocument.pub/reader033/viewer/2022050817/55660e2dd8b42aa6628b5376/html5/thumbnails/13.jpg)
equal eachの疑似コード equal eachの疑似コード
str : 検索したい文字列
text : 検索対象のテキスト
str = "abc", text = "aXc";
IntRes1[0] = text[0] == aなのでtrue
IntRes1[1] = text[1] != bなのでfalse
IntRes1[2] = text[2] == cなのでtrue
/41 13
for (int j = 0; j < len; j++) { IntRes1[j] = text[j] == str[j]; }
2011/8/6
![Page 14: SSE4.2の文字列処理命令の紹介](https://reader033.fdocument.pub/reader033/viewer/2022050817/55660e2dd8b42aa6628b5376/html5/thumbnails/14.jpg)
equal orderedの疑似コード equal orderedの疑似コード
str : 検索したい文字列
text : 検索対象のテキスト
これは複雑なのでinvalid文字列の扱いを説明してから
/41 14
for (int j = 0; j < len; j++) { int tmp = 1; for (int i = 0; i < len - j; i++) { tmp &= text[j + i] == str[i]; } IntRes1[j] = tmp; }
2011/8/6
![Page 15: SSE4.2の文字列処理命令の紹介](https://reader033.fdocument.pub/reader033/viewer/2022050817/55660e2dd8b42aa6628b5376/html5/thumbnails/15.jpg)
imm8[5:4] imm8[5:4]
中間結果(IntRes1)に作用してIntRes2を作る
00b(positive polarity)
そのまま出力(IntRes2[i] = IntRes1[i])
01b(negative polarity)
反転して出力(IntRes2[i] = ~IntRes1[i])
10b(masked(+))
そのまま
01b(masked(-))
入力がinvalidならそのまま,そうでなければ反転
/41 15 2011/8/6
![Page 16: SSE4.2の文字列処理命令の紹介](https://reader033.fdocument.pub/reader033/viewer/2022050817/55660e2dd8b42aa6628b5376/html5/thumbnails/16.jpg)
imm8[6] imm8[6]
最終結果操作
IntRes2からecxかxm0に出力するデータを作る
pcmpestri, pcmpistriに対する設定
0ならIntRes2のLSBが入る
1ならIntRes2のMSBが入る
ただしIntRes2 == 0なら16(byteのとき)か8(wordのとき)
pcmpestrm, pcmpistrmに対する設定
0ならIntRes2が0拡張されてxmm0に入る.
1ならIntRes2の各ビットが入力データの型の大きさにしたがってbyteかword単位のマスクに拡張されてxmm0に入る.
/41 16 2011/8/6
![Page 17: SSE4.2の文字列処理命令の紹介](https://reader033.fdocument.pub/reader033/viewer/2022050817/55660e2dd8b42aa6628b5376/html5/thumbnails/17.jpg)
invalid文字の扱い invalid文字の扱い
invalid文字
文字列の比較において,途中に'¥0'が出たり,長さが短くて終わってしまったときの残りの文字たちのこと
invalid文字と通常の文字との演算ルール
/41 17
xm1(str) xm2(text) equal any ranges equal each equal
orderd
valid valid 通常通り 通常通り 通常通り 通常通り
valid invalid 常に0 常に0 常に0 常に0
invalid valid 常に0 常に0 常に0 常に1
invalid invalid 常に0 常に0 常に1 常に1
2011/8/6
![Page 18: SSE4.2の文字列処理命令の紹介](https://reader033.fdocument.pub/reader033/viewer/2022050817/55660e2dd8b42aa6628b5376/html5/thumbnails/18.jpg)
equal orderedの例 equal orderedの例
src = "ABCA¥0XYZ", text = "BABCAB¥0S";
'¥0'の後ろはinvalid
T:True, F:False, fT:force True, fF:force Flase /41 18
text文字列
7 6 5 4 3 2 1 0(j)
S ¥0 B A C B A B
src
文字列
0(i) A fF fF F T F F T F
1 B fF fF T F F T F x
2 C fF fF F F T F x x
3 A fF fF F T F x x x
4 ¥0 fT fT fT fT fT x x x
5 X fT fT fT x x x x x
6 Y fT fT x x x x x x
7 Z fT x x x x x x x
F T F F F F F F
IntRes1
2011/8/6
http://journal.mycom.co.jp/articles/2008/04/10/idf09/008.html
![Page 19: SSE4.2の文字列処理命令の紹介](https://reader033.fdocument.pub/reader033/viewer/2022050817/55660e2dd8b42aa6628b5376/html5/thumbnails/19.jpg)
フラグレジスタ フラグレジスタ
フラグレジスタは次のように変化する
pcmpXstrY xm0, xm1, imm8
/41 19
pcmpestri / pcmpestrm pcmpistri / pcmpistrm
CF IntRes2 != 0
ZF |edx| < 16 xm1のいずれかが0なら1, そうでなければ0
SF |eax| < 16 xm0のいずれかが0なら1, そうでなければ0
OF IntRes2[0]
2011/8/6
![Page 20: SSE4.2の文字列処理命令の紹介](https://reader033.fdocument.pub/reader033/viewer/2022050817/55660e2dd8b42aa6628b5376/html5/thumbnails/20.jpg)
SSE4.2を使ったstrlen SSE4.2を使ったstrlen
pcmpistriを選択
0終端文字列を扱い,位置をビットスキャンで取る
imm8[1:0] = 0
符号無しbyte単位
'¥0'を探す
1~255にマッチしないものを見つけると考える
範囲指定なのでrangesを使う(imm8[3:2] = 01b)
中間結果操作でビット反転をするのでimm8[5:4]=01b
最終結果の下から初めての1を見つけるのでimm8[6]=0
よってimm8=0b010100=0x14となる
16byteの中に'¥0'がなければZF = 0 /41 20 2011/8/6
![Page 21: SSE4.2の文字列処理命令の紹介](https://reader033.fdocument.pub/reader033/viewer/2022050817/55660e2dd8b42aa6628b5376/html5/thumbnails/21.jpg)
SSE4.2を使ったstrlen SSE4.2を使ったstrlen
よってコードは次のようになる
とても簡単
/41 21
mov(eax, 0xff01); // { '0x01', '0xff', '¥0' }; movd(xm0, eax); // xm0にrangesの文字列を設定 mov(a, p); // 文字列の先頭 jmp(".in"); L("@@"); add(a, 16); L(".in"); pcmpistri(xm0, ptr [a], 0x14); // 比較して jnz("@b"); add(a, c); sub(a, p); ret();
2011/8/6
![Page 22: SSE4.2の文字列処理命令の紹介](https://reader033.fdocument.pub/reader033/viewer/2022050817/55660e2dd8b42aa6628b5376/html5/thumbnails/22.jpg)
ランダムな長さの文字列に対するbyteあたりの処理時間
SSE2バージョンは文字列長が長いと速い
32byte単位で処理しているため
glibcとSSE4.2は大体同じ
短いところでglibcが若干速いのは短いとき用の処理が入ってるから
ベンチマーク ベンチマーク
文字列平均長 5.04 66.62 261.78 1063.83
glibc 5.70 0.58 0.27 0.20
strlenC 6.92 1.89 1.41 1.24
SSE2 5.74 0.54 0.21 0.15
SSE4.2 5.03 0.69 0.29 0.21
/41 22 2011/8/6
![Page 23: SSE4.2の文字列処理命令の紹介](https://reader033.fdocument.pub/reader033/viewer/2022050817/55660e2dd8b42aa6628b5376/html5/thumbnails/23.jpg)
注意 注意
初期のintelのマニュアルは遅いバージョンが載っていた
http://journal.mycom.co.jp/articles/2008/04/10/idf09/008.html
これではpcmpistriの出力結果のecxが確定するまでaddで値を計算できないためスループットが下がるため
/41 23
L("@@"); add(a, ecx); // 16でなくてecx L(".in"); pcmpistri(xm0, ptr [a], 0x14); // 比較して jnz("@b");
2011/8/6
![Page 24: SSE4.2の文字列処理命令の紹介](https://reader033.fdocument.pub/reader033/viewer/2022050817/55660e2dd8b42aa6628b5376/html5/thumbnails/24.jpg)
intrinsic版の注意点 intrinsic版の注意点
pcmpistriなどの命令はecx(またはxm0)とフラグの両方を出力するがintrinsicは一つしか値をとれない
個別の値を取る関数が用意されている
_mm_cmpestraなどはAFを取得するものではない
AFは常に0に設定される
またhttp://msirocoder.blog35.fc2.com/blog-entry-65.html で触れられているようなResetでも無いように思う(自信無し)
/41 24
返り値 pcmpestri pcmpestrm pcmpistri pcmpistrm
ecx/xmm0 _mm_cmpestri _mm_cmpestrm _mm_cmpistri _mm_cmpistrm
CF = 0 && ZF = 0 _mm_cmpestra _mm_cmpistra
CF _mm_cmpestrc _mm_cmpistrc
OF _mm_cmpestro _mm_cmpistro
SF _mm_cmpestrs _mm_cmpistrs
ZF _mm_cmpestrz _mm_cmpistrz
2011/8/6
![Page 25: SSE4.2の文字列処理命令の紹介](https://reader033.fdocument.pub/reader033/viewer/2022050817/55660e2dd8b42aa6628b5376/html5/thumbnails/25.jpg)
SSE4.2 intrinsic版strlen SSE4.2 intrinsic版strlen
msiroさんのwhile()の方が簡潔でよさそう
asmコードと実行時間は同じようなものになる /41 25
#ifdef _WIN32 #include <intrin.h> #else #include <x86intrin.h> #endif size_t strlenSSE42_C(const char* top) { const __m128i im = _mm_set1_epi32(0xff01); const char *p = top - 16; do { p += 16; } while (!_mm_cmpistrz(im, *(__m128i*)p, 0x14)); // ZF p += _mm_cmpistri(im, *(__m128i*)p, 0x14); // get ecx return p - top; }
2011/8/6
*(__m128i*)pはmovdqaが生成される可能性がある
_mm_loadu_si128((__m128i*)p)を使うべき
(他のスライドも同様) cf.
http://homepage1.nifty.com/herumi/diary/1108.html#8
![Page 26: SSE4.2の文字列処理命令の紹介](https://reader033.fdocument.pub/reader033/viewer/2022050817/55660e2dd8b42aa6628b5376/html5/thumbnails/26.jpg)
生成コード(by gcc 4.6.0) 生成コード(by gcc 4.6.0)
_mm_cmpistrzと_mm_cmpistriの両方が一つのpcmpistriにまとめられている
よかった.たいしたもんだ
/41 26
lea rdx, [rdi-16] movdqa xmm0, XMMWORD PTR .LC0[rip] jmp .L11 // align16 .L12: mov rdx, rax .L11: lea rax, [rdx+16] pcmpistri xmm0, XMMWORD PTR [rdx+16], 20 jne .L12
2011/8/6
![Page 27: SSE4.2の文字列処理命令の紹介](https://reader033.fdocument.pub/reader033/viewer/2022050817/55660e2dd8b42aa6628b5376/html5/thumbnails/27.jpg)
strchr by SSE4.2 strchr by SSE4.2
ナイーブな実装
imm8の選択
符号無しbyte単位でimm8[1:0] = 0
集計はequal anyでimm8[3:2] = 0
文字列は"(char)c";
中間結果と最終結果は何もしないimm8[6:5:4] = 0
/41 27
const char *strchr_C(const char *p, int c) { while (*p) { if (*p == (char)c) return p; p++; } return 0; }
2011/8/6
![Page 28: SSE4.2の文字列処理命令の紹介](https://reader033.fdocument.pub/reader033/viewer/2022050817/55660e2dd8b42aa6628b5376/html5/thumbnails/28.jpg)
何のフラグでループするか 何のフラグでループするか
IntRes2 != 0なら文字を発見したのでループ脱出
CFを確認する
次に文字列が終了したかをZFで確認する
CF = 0 && ZF = 0のとき,すなわちjaとすればよい
/41 28
#lp pcmpistri xm0, ptr [p], 0 jc #found jnz #lp
#lp p += 16 pcmpistri xm0, ptr [p], 0 ja #lp jnc #notfound #found
2011/8/6
![Page 29: SSE4.2の文字列処理命令の紹介](https://reader033.fdocument.pub/reader033/viewer/2022050817/55660e2dd8b42aa6628b5376/html5/thumbnails/29.jpg)
コードとベンチマーク コードとベンチマーク
Xeon strchrLIB strchr_C strchrSSE42
clk 0.459 3.012 0.252
/41 29
const char *strchrSSE42_C(const char* p, int c) { const __m128i im = _mm_set1_epi32(c & 0xff); while (_mm_cmpistra(im, *(const __m128i*)p, 0)) { p += 16; } if (_mm_cmpistrc(im, *(const __m128i*)p, 0)) { return p + _mm_cmpistri(im, *(const __m128i*)p, 0); } return 0; }
2011/8/6
![Page 30: SSE4.2の文字列処理命令の紹介](https://reader033.fdocument.pub/reader033/viewer/2022050817/55660e2dd8b42aa6628b5376/html5/thumbnails/30.jpg)
範囲指定への拡張 範囲指定への拡張
if ((unsigned char)(*p - c1) <= (unsigned char)(c2 - c1)) return p; により1割ぐらい早くなるが
SSE4.2版はequal anyをrangesにするだけ /41 30
const char *findRange_C(const char* p, char c1, char c2) { const unsigned char *up = (const unsigned char *)p; unsigned char uc1 = c1; unsigned char uc2 = c2; while (*up) { if (uc1 <= *up && *up <= uc2) return (const char*)up; up++; } return 0; }
2011/8/6
![Page 31: SSE4.2の文字列処理命令の紹介](https://reader033.fdocument.pub/reader033/viewer/2022050817/55660e2dd8b42aa6628b5376/html5/thumbnails/31.jpg)
findRange by SSE4.2 findRange by SSE4.2
/41 31
const char *findRange_SSE42(const char* p, char c1, char c2) { const __m128i im = _mm_set1_epi32( ((unsigned char)c1) | (((unsigned char)c2) << 8) ); while (_mm_cmpistra(im, *(const __m128i*)p, 4)) { p += 16; } if (_mm_cmpistrc(im, *(const __m128i*)p, 4)) { return p + _mm_cmpistri(im, *(const __m128i*)p, 4); } return 0; }
i7 findRange_C findRange2_C findRange_SSE42
clk 2.310 2.055 0.214
2011/8/6
![Page 32: SSE4.2の文字列処理命令の紹介](https://reader033.fdocument.pub/reader033/viewer/2022050817/55660e2dd8b42aa6628b5376/html5/thumbnails/32.jpg)
単語のカウント 単語のカウント
ここでは英数字とアポストロフィの連続を単語とし,その個数を数える(インテルのマニュアルより)
インテルの比較用Cコードはかなりトリッキー
だが,凄く速いというわけでもない(おもしろいけど)
/41 32
size_t countWord_C(const char *p) { static const char alp_map8[32] = { 0, 0, 0, 0, 0x80, 0, 0xff, 0x3, 0xfe, 0xff, 0xff, 0x7, 0xfe, 0xff, 0xff, 0x7 }; size_t i = 1, cnt = 0; unsigned char cc, cc2; bool flag[3]; cc2 = cc = p[0]; flag[1] = alp_map8[cc >> 3] & (1 << (cc & 7)); while (cc2) { cc2 = p[i]; flag[2] = alp_map8[cc2 >> 3] & (1 << (cc2 & 7)); if (!flag[2] && flag[1]) cnt++; flag[1] = flag[2]; i++; } return cnt; }
2011/8/6
![Page 33: SSE4.2の文字列処理命令の紹介](https://reader033.fdocument.pub/reader033/viewer/2022050817/55660e2dd8b42aa6628b5376/html5/thumbnails/33.jpg)
多少最適化したもの 多少最適化したもの
今回はこれを使う(2倍程度速い)
/41 33
static char alnumTbl2[256]; // 単語になる文字だけ1, それ以外は0 size_t countWord_C2(const char *p){ size_t count = 0; unsigned char c = *p++; char prev = alnumTbl2[c]; while (c) { c = *p++; char cur = alnumTbl2[c]; if (!cur && prev) { count++; } prev = cur; } return count; }
2011/8/6
![Page 34: SSE4.2の文字列処理命令の紹介](https://reader033.fdocument.pub/reader033/viewer/2022050817/55660e2dd8b42aa6628b5376/html5/thumbnails/34.jpg)
SSE4.2 intrinsc版countWord SSE4.2 intrinsc版countWord
msiroさんのを少し変更
/41 34
MIE_ALIGN(16) static const char alnumTbl[16] = { '¥'', '¥'', '0', '9', 'A', 'Z', 'a', 'z', '¥0' }; size_t countWord_SSE42(const char *p) { const __m128i im = *(const __m128i*)alnumTbl; __m128i ret, x, prev = _mm_setzero_si128(); size_t count = 0; goto SKIP; do { p += 16; SKIP: ret = _mm_cmpistrm(im, *(const __m128i*)p, 0x4); x = _mm_slli_epi16(ret, 1); x = _mm_or_si128(prev, x); prev = _mm_srli_epi32(ret, 15); x = _mm_xor_si128(x, ret); count += _mm_popcnt_u32(_mm_cvtsi128_si32(x)); } while (!_mm_cmpistrz(im, *(const __m128i*)p, 0x4)); return count / 2; }
2011/8/6
![Page 35: SSE4.2の文字列処理命令の紹介](https://reader033.fdocument.pub/reader033/viewer/2022050817/55660e2dd8b42aa6628b5376/html5/thumbnails/35.jpg)
countWord_SSE42の解説 countWord_SSE42の解説
', 0-9, A-Z, a-zの範囲にあるもの(C)を探す
rangesを使う
(C)とそれ以外の境界は0から1, 1から0に変化する
エッジは1bitずらしてxorすれば検出できる
/41 35
1 1 1 0 0 1 0 0
1 1 1 0 0 1 0 0 0
次回使う 0 0 1 0 1 1 0 0
xor
ビットの立っている個数を数える
倍数えることになるので最後に2で割る
2011/8/6
![Page 36: SSE4.2の文字列処理命令の紹介](https://reader033.fdocument.pub/reader033/viewer/2022050817/55660e2dd8b42aa6628b5376/html5/thumbnails/36.jpg)
SSE4.2 intrinsc版countWord SSE4.2 intrinsc版countWord
(C)に入る範囲を配列で指定する
符号無しbyte単位なのでimm8[1:0] = 0
集計方法はranges imm8[3:2] = 01b
中間操作:そのまま出力 imm8[5:4] = 0
最終操作:そのまま出力 imm8[6] = 0
よってimm8 = 0b100 = 4
/41 36
MIE_ALIGN(16) static const char alnumTbl[16] = { '¥'', '¥'', '0', '9', 'A', 'Z', 'a', 'z', '¥0' }; const __m128i im = *(const __m128i*)alnumTbl;
2011/8/6
![Page 37: SSE4.2の文字列処理命令の紹介](https://reader033.fdocument.pub/reader033/viewer/2022050817/55660e2dd8b42aa6628b5376/html5/thumbnails/37.jpg)
SSE4.2 intrinsc版countWord SSE4.2 intrinsc版countWord
retの各ビットに(c in (C)) ? 1 : 0が入る
x = ret << 1;
x = x | prev; // 前回の残りの1bit
prev = ret >> 15;
x = x ^ ret.
count += xのビットの数
文字列の中に'¥0'が見つかるまでループ /41 37
do { p += 16; ret = _mm_cmpistrm(im, *(const __m128i*)p, 0x4); x = _mm_slli_epi16(ret, 1); x = _mm_or_si128(prev, x); prev = _mm_srli_epi32(ret, 15); x = _mm_xor_si128(x, ret); count += _mm_popcnt_u32(_mm_cvtsi128_si32(x)); } while (!_mm_cmpistrz(im, *(const __m128i*)p, 0x4));
2011/8/6
![Page 38: SSE4.2の文字列処理命令の紹介](https://reader033.fdocument.pub/reader033/viewer/2022050817/55660e2dd8b42aa6628b5376/html5/thumbnails/38.jpg)
ベンチマーク ベンチマーク
i7 インテルオリジナル 改良版 intrinsic版
clk 854 456 42
/41 38
一桁速い!
生成コードを見てみる
あれ,pcmpistrmが2回
pcmpistrmはフラグを変える
真ん中のadd(a, d);が邪魔
lea(a, ptr [a + d]);にすればOK?
L("@@"); add(p, 16); movdqa(xm1, ptr [p]); pcmpistrm(xm2, xm1, 4); movdqa(xm4, xm0); psllw(xm4, 1); por(xm4, xm3); pxor(xm4, xm0); movd(d, xm4); movdqa(xm3, xm0); popcnt(d, d); add(a, d); psrld(xm3, 15); pcmpistrm(xm2, xm1, 4); jnz("@b");
2011/8/6
![Page 39: SSE4.2の文字列処理命令の紹介](https://reader033.fdocument.pub/reader033/viewer/2022050817/55660e2dd8b42aa6628b5376/html5/thumbnails/39.jpg)
実はpopcntもフラグをいじる
命令の順序を入れ換えてしまおう
速くなった!
改良 改良
i7 インテルオリジナル 改良版 intrinsic版 改良版
clk 854 456 42 33
/41 39
L("@@"); movdqa(xm4, xm0); psllw(xm4, 1); por(xm4, xm3); movdqa(xm3, xm0); pxor(xm4, xm0); psrld(xm3, 15); movd(d, xm4); popcnt(d, d); add(p, 16); add(a, d); pcmpistrm(xm2, ptr [p], 4); jnz("@b");
2011/8/6
![Page 40: SSE4.2の文字列処理命令の紹介](https://reader033.fdocument.pub/reader033/viewer/2022050817/55660e2dd8b42aa6628b5376/html5/thumbnails/40.jpg)
Xeonでは遅くなっていた
いろいろ難しい…
改悪? 改悪?
i7 インテルオリジナル 改良版 intrinsic版 改良版
clk 854 456 42 33
/41 40
Xeon インテルオリジナル 改良版 intrinsic版 改良版?
clk 1714 874 46 53
2011/8/6
![Page 41: SSE4.2の文字列処理命令の紹介](https://reader033.fdocument.pub/reader033/viewer/2022050817/55660e2dd8b42aa6628b5376/html5/thumbnails/41.jpg)
まとめ まとめ
SSE4.2の命令の紹介
アライメントを気にする必要がない
超高機能な文字マッチパターン
bsf, bsr相当の機能も持つ
intrinsic版はフラグとecx/xm0の両方必要
手動最適化の余地あり?
プロセッサによって結構性能が違うこともある
/41 41 2011/8/6