More Related Content Similar to SIMDで整数除算 (12) SIMDで整数除算5. おさらい・浮動小数点数の除算
• 直接除算する方法
– __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
7. 整数の除算
• 除算命令
– ない…
• 逆数命令
– ない…
• 右シフト命令
– 2の冪乗でしか使えない
• ⇒ なんとか自分で逆数を求めるしかない
– でも逆数は1以下なので、整数では表現できない…
(´・ω・`)Shobomaru 7
8. 整数を固定小数として扱う
• 最上位ビット(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
9. 固定小数の掛け算
• 例:“8÷5”
– = 8×13037÷65536
• この除算は16bit論理右シフトで代用可能
“(8*13037) >> 16”
– = 1.5999755859375
– 整数でキャストして答えは“1”
_,,-―=''' ̄ ___,,-―――='' ̄ __,-―='' ̄ /
_,,-―=''' ̄ _,,-―='' ̄ ヽ / +
 ̄ ̄ _,,-―=''' ̄ \ / . . . .
,,-='' ̄ _ノ ,_ノ ヽ / . 。. ★ ☆
,,,-'' / iニ)ヽ, /rj:ヽヽ ヽ/ 。. .
-―'' ̄ ;〈 !:::::::c! |___,/' {.::::::;、! } | -┼- 丿~~~| |~~~~~| __ ■
. |. (つ`''" | / `'ー''(つ. |. -┼- /~~~~/ 丿 | 丿 ▼ ▼
| . ///// | / /// | | 丿 / 丿 ● ●
ヽ γ´~⌒ヽ. | / /
――ヽ / ヽ | / /⌒ヽ、
\/ | |_/ / ヽ
(´・ω・`)Shobomaru 9
10. 精度の問題
• この方法で“10÷5”を計算すると…
– 10×13037÷65536
– = 1.99996948242188
– 整数でキャストして“1”…???
,,-―=''' ̄ ___,,-―――='' ̄ __,-―='' ̄ /
_,,-―=''' ̄ _,,-―='' ̄ ヽ / +
 ̄ ̄ _,,-―=''' ̄ \ / . . . .
,,-='' ̄ _ノ ,_ノ ヽ / . 。. ★ ☆
,,,-'' / iニ)ヽ, /rj:ヽヽ ヽ/ 。. .
-―'' ̄ ;〈 !:::::::c! ' {.::::::;、! 〉 |  ̄ ̄| _|_ 丿 |~~~~~|
. | (つ`''" __ `'ー''(つ | | | /|. 丿 | 丿
| ///// | | /// | __| 丿 | / 丿
ヽ γ´~⌒ヽ. / | /
――ヽ / ヽ / | /⌒ヽ、
\/ | | ̄ ̄ ̄ ̄| / ヽ
• なぜ?
– 逆数の丸め誤差を考慮していないから
(´・ω・`)Shobomaru 10
11. Terje Mathisenのアルゴリズム(?) [1]
• 逆数の小数点の位置をできるだけ右にずらす
– 代償として、乗算後の論理右シフトの量を調整する
xを割られる数、dを割る数とするとき、
b = (有効ビット数) – 1
r=w+b
f = 2r / d
もしfが整数ならば、case Aへ
もしfの小数部が0.5未満ならば、case Bへ
もしfの小数部が0.5を超えるならば、case Cへ
case A: result = x SHR b
case B: result = ( ( x + 1 ) * f ) SHR r
ただし、fは切り捨て
case C: result = ( x * f ) SHR r
ただし、fは切り上げ
SHRは論理右シフトのこと
(´・ω・`)Shobomaru 11
12. C言語のプログラム(1)
引数 意味
int short_rcp( div 割る数
unsigned short div, rcp 逆数
unsigned short *rcp,
int *shift, shift 論理右シフト量
unsigned short *bias ) bias 補正
{
int b = 0; 戻り値 divが2の冪乗か否か
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 );
(´・ω・`)Shobomaru 12
13. C言語のプログラム(2)
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;
}
} (´・ω・`)Shobomaru 13
14. C言語のプログラム(3)
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言語の整数拡張ルールの関係で、乗算が符号付きに
なってしまう
– 楽しいアセンブラプログラミングが待ち受ける
(´・ω・`)Shobomaru 14
15. 逆数求めるの面倒すぎじゃね?
• だから言っただろう、
「ただし、除数が固定なら」と。
– 面倒な計算も初回だけなら我慢できる
• 除数が固定なら、変数pow2も不変なので、
条件分岐のコストは考えなくてよい
– Branch Target Bufferのない糞CPUなんぞ知らん
• 整数拡張もSSEなら自分で操作できる
– アセンブラいらない!
(´・ω・`)Shobomaru 15
16. SSE2を使ったプログラム
__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は終了
(´・ω・`)Shobomaru 16
18. 実は賢いコンパイラ
• 実は、C言語で定数の除算式を書くと
勝手に乗算+論理右シフトにしてくれる
– 割る数が2の冪乗なら右シフトだけ
volatile unsigned int dividend = 8;
unsigned int ans = dividend / 5;
mov ecx, ***
mov eax, 0CCCCCCDh
mul eax, ecx
shr edx, 2
(Visual C++ 10.0 / Release)
(´・ω・`)Shobomaru 18
19. 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
20. BSR命令を使った最適化(2)
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;
• といっても逆数を求める部分なので、
効果はほとんどない
(´・ω・`)Shobomaru 20
21. _mm_set1_epi16()
• 実は複数の命令に変換されてしまう
– mov + punpcklwd + pshufd
• SSSE3なら_mm_shuffle_pi8()、
AVX1なら_mm_broadcastw_epi16()
でpunpcklwdは不要になる
– movは消せないけど、IvyBridgeからmovはリネームス
テージで消滅するらしいから、多分気にしないでいい
• AMD?なにそれおいしいの?
(´・ω・`)Shobomaru 21
25. ARM NEONでは…
• NEONも整数の除算はない
• 乗算はある
– VQDMULH命令
• ただし、符号つき16/32bitのみ…
– VMULL命令
• 符号なし8/16/32bit、後で自分で上位ビットを取り出す
• なぜか整数の逆数命令もある
– VRECPE/VRECPS命令
• 符号なし32bit(と32bit小数)
– 精度とかはよく知らない
• Newton-Raphson法が必要?試すのマンドクセ(‘A`)
(´・ω・`)Shobomaru 25
27. 参考文献
1. Optimizing subroutines in assembly language
http://www.agner.org/optimize/optimizing_assem
bly.pdf
• というか、ほぼパクリです。すみません。
(´・ω・`)Shobomaru 27
28. ライセンス
• このスライドは全て、
「クリエイティブ・コモンズ 表示 2.1」
の下で提供しています
(ただし引用した図・文字を除く)
(´・ω・`)Shobomaru 28