28
SIMD ででででで @Shobomaru

SIMDで整数除算

Embed Size (px)

DESCRIPTION

 

Citation preview

Page 1: SIMDで整数除算

SIMD で整数除算@Shobomaru

Page 2: SIMDで整数除算

更新履歴

• 2012/11/01 v1.0Slideshare のテストも兼ねて試験的に公開

( ´ ・ ω ・ ` ) Shobomaru 2

Page 3: SIMDで整数除算

2011 年 12 月下旬くらいの出来事

( ´ ・ ω ・ ` ) Shobomaru 3

( ^o^) SIMD で遊ぼう!

( ˘⊖˘) 。 o( 待てよ?浮動小数点数の除算は rcpss 命令だけど、整数の除算は? )

| In●el |┗(☋ ` )┓ 三

( ◠‿◠ )☛ そこに気づいたか・・・消えてもらう

▂▅▇█▓▒░(’ω’)░▒▓█▇▅▂ うわああああああ

Page 4: SIMDで整数除算

詰んだ?

• 詰んだ。終了。解散。

• … ただし、除数が固定かつ 16bit 以下ならまだ希望がある

( ´ ・ ω ・ ` ) Shobomaru 4

Page 5: SIMDで整数除算

おさらい・浮動小数点数の除算

• 直接除算する方法– __m128 _mm_div_ps( __m128 a, __m128 b );

• DIVPS xmm1, xmm2 (SSE1)• 遅い代わりに精度が高い

– スループットは 1/20 、遅すぎ

• 逆数を求めて乗算にする方法– __m128 _mm_rcp_ps( __m128 a );

• RCPPS xmm1, xmm2 (SSE1)• 速い代わりに精度が低い

– その後乗算も必要

( ´ ・ ω ・ ` ) Shobomaru 5

Page 6: SIMDで整数除算

おさらい・逆数による除算から乗算への変換

• 例:“ 8.0÷5.0=1.6”– = 8.0× ( 1.0÷5.0 )– = 8.0×0.2 // ← この“ 0.2” を求めるのが逆数命令– = 1.6

( ´ ・ ω ・ ` ) Shobomaru 6

Page 7: SIMDで整数除算

整数の除算

• 除算命令– ない…

• 逆数命令– ない…

• 右シフト命令– 2 の冪乗でしか使えない

• ⇒  なんとか自分で逆数を求めるしかない– でも逆数は 1 以下なので、整数では表現できない…

( ´ ・ ω ・ ` ) Shobomaru 7

Page 8: SIMDで整数除算

整数を固定小数として扱う

• 最上位ビット (MSB) が 0.5 、 MSB の隣が0.25 、その隣が 0.125…

• 例:“ 0.2”– = 0.125 + 0.0625 + 0.00078125 + 0.000390625

+ …– 2 進数表現では(とりえず 16bit で)

“ 0.0011001100110011”– 小数点以下を固定小数として取り出す

“ 13037”– 固定小数なので、 2^16 である“ 65536” が 1 を表す

• 13037÷65536 = 0.199996948242188≒0.2( ´ ・ ω ・ ` ) Shobomaru 8

Page 9: SIMDで整数除算

固定小数の掛け算

• 例:“ 8÷5”– = 8×13037÷65536

• この除算は 16bit 論理右シフトで代用可能“ (8*13037) >> 16”

– = 1.5999755859375– 整数でキャストして答えは“ 1”

( ´ ・ ω ・ ` ) Shobomaru 9

_,,-― = '''  ̄      ___ ,,-――― = ''  ̄ _ _,-― = ''  ̄   /   _ ,,-― = '''  ̄        _ ,,-― = ''  ̄ ヽ       /   + ̄ ̄        _ ,,-― = '''  ̄          \    /   . .   .    .      ,,- = ''  ̄    _ノ          , _ノ ヽ  /     .   。 . ★   ☆    ,,,-''         / i ニ ) ヽ ,          /rj: ヽヽ ヽ/     。 .      .-―''  ̄         ; 〈  !:::::::c!   | ___ ,/' {.::::::; 、 ! }   |    -┼-  丿 ~~~| |~~~~~| __ ■ ■.    |.             ( つ `''"   |      /   `' ー ''( つ .   |.    -┼- /~~~~/     丿  | 丿 ▼ ▼   |   .         /////       |     /       /// |      |      丿   /  丿  ● ●  ヽ    γ´~⌒ ヽ .          |    /           /―― ヽ   /       ヽ       |   /          /⌒ ヽ、    \ /         |         |_/          /    ヽ

Page 10: SIMDで整数除算

精度の問題

• この方法で“ 10÷5” を計算すると…– 10×13037÷65536– = 1.99996948242188– 整数でキャストして“ 1”… ???

• なぜ?– 逆数の丸め誤差を考慮していないから

( ´ ・ ω ・ ` ) Shobomaru 10

,,-― = '''  ̄      ___ ,,-――― = ''  ̄ _ _,-― = ''  ̄   /   _ ,,-― = '''  ̄        _ ,,-― = ''  ̄ ヽ       /   + ̄ ̄        _ ,,-― = '''  ̄          \    /   . .   .    .      ,,- = ''  ̄    _ノ         , _ノ  ヽ  /     .   。 . ★   ☆    ,,,-''         / i ニ ) ヽ ,         /rj: ヽヽ  ヽ/     。 .      .-―''  ̄         ; 〈  !:::::::c!        ' {.::::::; 、 ! 〉   |    ̄ ̄ |  _ | _  丿 |~~~~~| __ .  ■ ■.    |              ( つ `''"    __   `' ー ''( つ    |      |    |  / |.     丿  | 丿 ▼ ▼   |       /////          |      |       ///   |   __ |   丿     |    /   丿   ● ●  ヽ    γ´~⌒ ヽ .         /       |         /―― ヽ   /       ヽ      /        |        /⌒ ヽ、    \ /         |      |  ̄ ̄ ̄ ̄ |       /    ヽ

Page 11: SIMDで整数除算

Terje Mathisen のアルゴリズム (?)

• 逆数の小数点の位置をできるだけ右にずらす– 代償として、乗算後の論理右シフトの量を調整する

( ´ ・ ω ・ ` ) Shobomaru 11

x を割られる数、 d を割る数とするとき、b = ( 有効ビット数 ) – 1r = w + bf = 2r / dもし f が整数ならば、 case A へもし f の小数部が 0.5 未満ならば、 case B へもし f の小数部が 0.5 を超えるならば、 case C へcase A: result = x SHR bcase B: result = ( ( x + 1 ) * f ) SHR r

ただし、 f は切り捨てcase C: result = ( x * f ) SHR r

ただし、 f は切り上げSHR は論理右シフトのこと

[1]

Page 12: SIMDで整数除算

C 言語のプログラム (1)

( ´ ・ ω ・ ` ) Shobomaru 12

int short_rcp(unsigned short div,unsigned short *rcp,int *shift,unsigned short *bias ){

int b = 0;for( int i = 0; i < 16; i++ ) {

if( ( ( div >> ( 15 - i ) ) & 0x1 ) == 1 ) {b = 15 - i;break;

}}

unsigned int r = 16 + b;unsigned int r2 = 1 << r;double f = (double)r2 / div;double fm = fmod( f, 1.0 );

引数 意味

div 割る数

rcp 逆数

shift 論理右シフト量

bias 補正

戻り値 div が 2 の冪乗か否か

Page 13: SIMDで整数除算

C 言語のプログラム (2)

( ´ ・ ω ・ ` ) Shobomaru 13

if( fm == 0.0 ){

*shift = b;*rcp = 1;*bias = 0;return 1;

}else if( fm < 0.5 ){

*shift = b;*rcp = (unsigned short)f;*bias = 1;return 0;

}else{

*shift = b;*rcp = (unsigned short)( f + 0.5 );*bias = 0;return 0;

}}

Page 14: SIMDで整数除算

C 言語のプログラム (3)

( ´ ・ ω ・ ` ) Shobomaru 14

const unsigned short dividend = 10;const unsigned short divisor = 5;unsigned short rcp;int shift;unsigned short bias;

int ans;int pow2 = short_rcp( divisor, &rcp, &shift, &bias );if( pow2 ) ans = dividend >> shift;else ans = ( ( dividend + bias ) * rcp ) >> ( shift + 16 );

• ans は期待通り” 2”• 割られる数が 32769 以上のとき、不正な解を出

す– C 言語の整数拡張ルールの関係で、乗算が符号付きに

なってしまう– 楽しいアセンブラプログラミングが待ち受ける

Page 15: SIMDで整数除算

逆数求めるの面倒すぎじゃね?

• だから言っただろう、「ただし、除数が固定なら」と。– 面倒な計算も初回だけなら我慢できる

• 除数が固定なら、変数 pow2 も不変なので、条件分岐のコストは考えなくてよい– Branch Target Buffer のない糞 CPU なんぞ知らん

• 整数拡張も SSE なら自分で操作できる– アセンブラいらない!

( ´ ・ ω ・ ` ) Shobomaru 15

Page 16: SIMDで整数除算

SSE2 を使ったプログラム

( ´ ・ ω ・ ` ) Shobomaru 16

__m128i mdivident;__m128i mrcp = _mm_set1_epi16( rcp );__m128i mbias = _mm_set1_epi16( bias );__m128i mans;

mdivident = _mm_load_si128( [ メモリアドレス ] );if( pow2 ) mans = _mm_srli_epi16( mdibident, shift );else mans = _mm_srli_epi16( _mm_mulhi_epu16(

_mm_add_epi16( mdivident, mbias ), mrcp ), shift );

• _mm_mulhi_epi16 () は乗算後の上位ビットを返す– 上位型への拡張は要らない– 【悲報】上位ビットを返す乗算は、符号つき or なしの

16bit 整数しかない• 8bit なら 16bit に unpack 、 32bit は終了

Page 17: SIMDで整数除算

割る数が USHORT_MAX のときの問題

• bias が 1 、 divisor が 65535 のとき、直後の加算で整数オーバーフローによって不正な解になる

• どうしようもないので、分岐してスカラで計算するか、型昇格するかで回避するしかない– できたら USHORT_MAX が来ないようにする

( ´ ・ ω ・ ` ) Shobomaru 17

Page 18: SIMDで整数除算

実は賢いコンパイラ

• 実は、 C 言語で定数の除算式を書くと勝手に乗算+論理右シフトにしてくれる– 割る数が 2 の冪乗なら右シフトだけ

( ´ ・ ω ・ ` ) Shobomaru 18

volatile unsigned int dividend = 8;unsigned int ans = dividend / 5;

mov ecx, ***mov eax, 0CCCCCCDhmul eax, ecxshr edx, 2

(Visual C++ 10.0 / Release)

Page 19: SIMDで整数除算

BSR 命令を使った最適化 (2)

• 実は、有効ビット数の計算は x86専用命令がある

• BSR 命令– ただし、 0 (立っているビットがない)は未定義値

• 除算なので、そもそも逆数に 0 が入ってくる時点でおかしい– assert() なり throw なり自分で例外処理する

• 0 が未定義でない LZCNT 命令もあるが、 AMD専用 (SSE4a)

– イントリンシック命令• _BitScanReverse() (Visual C++)  ※ intrin.h を include• _bit_scan_reverse() (Intel C++ Compiler)• __builtin_clz() (GNU gcc)

– ARM とか MIPS とかでも使える– xor 16 を取る必要あり?(要確認)

( ´ ・ ω ・ ` ) Shobomaru 19

Page 20: SIMDで整数除算

BSR 命令を使った最適化 (2)

( ´ ・ ω ・ ` ) Shobomaru 20

unsigned long bl;_BitScanReverse( &bl, div );

//int b = 16;//for( int i = 0; i < 16; i++ ) {// if( ( ( div >> ( 15 - i ) ) & 0x1 ) == 1 ) {// b = 15 - i;// break;// }//}int b = bl;

• といっても逆数を求める部分なので、効果はほとんどない

Page 21: SIMDで整数除算

_mm_set1_epi16()

• 実は複数の命令に変換されてしまう– mov + punpcklwd + pshufd

• SSSE3 なら _mm_shuffle_pi8() 、AVX1 なら _mm_broadcastw_epi16()で punpcklwd は不要になる– mov は消せないけど、 IvyBridge から mov はリネー

ムステージで消滅するらしいから、多分気にしないでいい• AMD ?なにそれおいしいの?

( ´ ・ ω ・ ` ) Shobomaru 21

Page 22: SIMDで整数除算

ベンチマーク

• …良く考えたら整数除算する機会ってなくね?

• てなわけで飽きました (:P

( ´ ・ ω ・ ` ) Shobomaru 22

Page 23: SIMDで整数除算

テーブル参照

• 除数固定、入力値の範囲が大きくなければ、SSE の代わりに除算結果のテーブルを作ってしまうのもアリ– テーブル参照なので、 SSE は使えない

( ´ ・ ω ・ ` ) Shobomaru 23

Page 24: SIMDで整数除算

問題点

• SSE/AVX の乗算は 16/32bit型だけ– 8bit型は pack/unpack で 16bit に変換する必要あり

• 符号つき整数は一工夫必要– 乗算を符号付き、シフトを算術シフトにすればいい?

• 試すのマンドクセ (‘A`)

( ´ ・ ω ・ ` ) Shobomaru 24

Page 25: SIMDで整数除算

ARM NEON では…

• NEON も整数の除算はない• 乗算はある– VQDMULH 命令

• ただし、符号つき 16/32bit のみ…– VMULL 命令

• 符号なし 8/16/32bit 、後で自分で上位ビットを取り出す

• なぜか整数の逆数命令もある– VRECPE/VRECPS 命令

• 符号なし 32bit (と 32bit 小数)– 精度とかはよく知らない

• Newton-Raphson 法が必要?試すのマンドクセ (‘A`)

( ´ ・ ω ・ ` ) Shobomaru 25

Page 26: SIMDで整数除算

まとめ

• SSE に整数の除算命令はない

• なんとか逆数を作ることで、乗算を使って除算の代用が可能

• それでも制約がいっぱい– 早く整数除算命令を作ってくれ

( ´ ・ ω ・ ` ) Shobomaru 26

Page 27: SIMDで整数除算

参考文献

1. Optimizing subroutines in assembly languagehttp://www.agner.org/optimize/optimizing_assembly.pdf

• というか、ほぼパクリです。すみません。

( ´ ・ ω ・ ` ) Shobomaru 27

Page 28: SIMDで整数除算

ライセンス

• このスライドは全て、「クリエイティブ・コモンズ 表示 2.1 」の下で提供しています(ただし引用した図・文字を除く)

( ´ ・ ω ・ ` ) Shobomaru 28