Upload
shobomaru
View
3.165
Download
4
Embed Size (px)
DESCRIPTION
Citation preview
SIMD で整数除算@Shobomaru
更新履歴
• 2012/11/01 v1.0Slideshare のテストも兼ねて試験的に公開
( ´ ・ ω ・ ` ) Shobomaru 2
2011 年 12 月下旬くらいの出来事
( ´ ・ ω ・ ` ) Shobomaru 3
( ^o^) SIMD で遊ぼう!
( ˘⊖˘) 。 o( 待てよ?浮動小数点数の除算は rcpss 命令だけど、整数の除算は? )
| In●el |┗(☋ ` )┓ 三
( ◠‿◠ )☛ そこに気づいたか・・・消えてもらう
▂▅▇█▓▒░(’ω’)░▒▓█▇▅▂ うわああああああ
詰んだ?
• 詰んだ。終了。解散。
• … ただし、除数が固定かつ 16bit 以下ならまだ希望がある
( ´ ・ ω ・ ` ) Shobomaru 4
おさらい・浮動小数点数の除算
• 直接除算する方法– __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
おさらい・逆数による除算から乗算への変換
• 例:“ 8.0÷5.0=1.6”– = 8.0× ( 1.0÷5.0 )– = 8.0×0.2 // ← この“ 0.2” を求めるのが逆数命令– = 1.6
( ´ ・ ω ・ ` ) Shobomaru 6
整数の除算
• 除算命令– ない…
• 逆数命令– ない…
• 右シフト命令– 2 の冪乗でしか使えない
• ⇒ なんとか自分で逆数を求めるしかない– でも逆数は 1 以下なので、整数では表現できない…
( ´ ・ ω ・ ` ) Shobomaru 7
整数を固定小数として扱う
• 最上位ビット (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
固定小数の掛け算
• 例:“ 8÷5”– = 8×13037÷65536
• この除算は 16bit 論理右シフトで代用可能“ (8*13037) >> 16”
– = 1.5999755859375– 整数でキャストして答えは“ 1”
( ´ ・ ω ・ ` ) Shobomaru 9
_,,-― = '''  ̄ ___ ,,-――― = ''  ̄ _ _,-― = ''  ̄ / _ ,,-― = '''  ̄ _ ,,-― = ''  ̄ ヽ / + ̄ ̄ _ ,,-― = '''  ̄ \ / . . . . ,,- = ''  ̄ _ノ , _ノ ヽ / . 。 . ★ ☆ ,,,-'' / i ニ ) ヽ , /rj: ヽヽ ヽ/ 。 . .-―''  ̄ ; 〈 !:::::::c! | ___ ,/' {.::::::; 、 ! } | -┼- 丿 ~~~| |~~~~~| __ ■ ■. |. ( つ `''" | / `' ー ''( つ . |. -┼- /~~~~/ 丿 | 丿 ▼ ▼ | . ///// | / /// | | 丿 / 丿 ● ● ヽ γ´~⌒ ヽ . | / /―― ヽ / ヽ | / /⌒ ヽ、 \ / | |_/ / ヽ
精度の問題
• この方法で“ 10÷5” を計算すると…– 10×13037÷65536– = 1.99996948242188– 整数でキャストして“ 1”… ???
• なぜ?– 逆数の丸め誤差を考慮していないから
( ´ ・ ω ・ ` ) Shobomaru 10
,,-― = '''  ̄ ___ ,,-――― = ''  ̄ _ _,-― = ''  ̄ / _ ,,-― = '''  ̄ _ ,,-― = ''  ̄ ヽ / + ̄ ̄ _ ,,-― = '''  ̄ \ / . . . . ,,- = ''  ̄ _ノ , _ノ ヽ / . 。 . ★ ☆ ,,,-'' / i ニ ) ヽ , /rj: ヽヽ ヽ/ 。 . .-―''  ̄ ; 〈 !:::::::c! ' {.::::::; 、 ! 〉 |  ̄ ̄ | _ | _ 丿 |~~~~~| __ . ■ ■. | ( つ `''" __ `' ー ''( つ | | | / |. 丿 | 丿 ▼ ▼ | ///// | | /// | __ | 丿 | / 丿 ● ● ヽ γ´~⌒ ヽ . / | /―― ヽ / ヽ / | /⌒ ヽ、 \ / | |  ̄ ̄ ̄ ̄ | / ヽ
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]
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 の冪乗か否か
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;
}}
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 言語の整数拡張ルールの関係で、乗算が符号付きに
なってしまう– 楽しいアセンブラプログラミングが待ち受ける
逆数求めるの面倒すぎじゃね?
• だから言っただろう、「ただし、除数が固定なら」と。– 面倒な計算も初回だけなら我慢できる
• 除数が固定なら、変数 pow2 も不変なので、条件分岐のコストは考えなくてよい– Branch Target Buffer のない糞 CPU なんぞ知らん
• 整数拡張も SSE なら自分で操作できる– アセンブラいらない!
( ´ ・ ω ・ ` ) Shobomaru 15
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 は終了
割る数が USHORT_MAX のときの問題
• bias が 1 、 divisor が 65535 のとき、直後の加算で整数オーバーフローによって不正な解になる
• どうしようもないので、分岐してスカラで計算するか、型昇格するかで回避するしかない– できたら USHORT_MAX が来ないようにする
( ´ ・ ω ・ ` ) Shobomaru 17
実は賢いコンパイラ
• 実は、 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)
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
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;
• といっても逆数を求める部分なので、効果はほとんどない
_mm_set1_epi16()
• 実は複数の命令に変換されてしまう– mov + punpcklwd + pshufd
• SSSE3 なら _mm_shuffle_pi8() 、AVX1 なら _mm_broadcastw_epi16()で punpcklwd は不要になる– mov は消せないけど、 IvyBridge から mov はリネー
ムステージで消滅するらしいから、多分気にしないでいい• AMD ?なにそれおいしいの?
( ´ ・ ω ・ ` ) Shobomaru 21
ベンチマーク
• …良く考えたら整数除算する機会ってなくね?
• てなわけで飽きました (:P
( ´ ・ ω ・ ` ) Shobomaru 22
テーブル参照
• 除数固定、入力値の範囲が大きくなければ、SSE の代わりに除算結果のテーブルを作ってしまうのもアリ– テーブル参照なので、 SSE は使えない
( ´ ・ ω ・ ` ) Shobomaru 23
問題点
• SSE/AVX の乗算は 16/32bit型だけ– 8bit型は pack/unpack で 16bit に変換する必要あり
• 符号つき整数は一工夫必要– 乗算を符号付き、シフトを算術シフトにすればいい?
• 試すのマンドクセ (‘A`)
( ´ ・ ω ・ ` ) Shobomaru 24
ARM NEON では…
• NEON も整数の除算はない• 乗算はある– VQDMULH 命令
• ただし、符号つき 16/32bit のみ…– VMULL 命令
• 符号なし 8/16/32bit 、後で自分で上位ビットを取り出す
• なぜか整数の逆数命令もある– VRECPE/VRECPS 命令
• 符号なし 32bit (と 32bit 小数)– 精度とかはよく知らない
• Newton-Raphson 法が必要?試すのマンドクセ (‘A`)
( ´ ・ ω ・ ` ) Shobomaru 25
まとめ
• SSE に整数の除算命令はない
• なんとか逆数を作ることで、乗算を使って除算の代用が可能
• それでも制約がいっぱい– 早く整数除算命令を作ってくれ
( ´ ・ ω ・ ` ) Shobomaru 26
参考文献
1. Optimizing subroutines in assembly languagehttp://www.agner.org/optimize/optimizing_assembly.pdf
• というか、ほぼパクリです。すみません。
( ´ ・ ω ・ ` ) Shobomaru 27
ライセンス
• このスライドは全て、「クリエイティブ・コモンズ 表示 2.1 」の下で提供しています(ただし引用した図・文字を除く)
( ´ ・ ω ・ ` ) Shobomaru 28