条件分岐とcmovとmaxps

24
条件分岐とcmovとmaxps 条件分岐とcmovとmaxps Cybozu Labs 2011/8/6 光成滋生 x86/x64最適化勉強会#1

Upload: mitsunari-shigeo

Post on 28-May-2015

4.131 views

Category:

Technology


7 download

TRANSCRIPT

Page 1: 条件分岐とcmovとmaxps

条件分岐とcmovとmaxps 条件分岐とcmovとmaxps

Cybozu Labs

2011/8/6 光成滋生

x86/x64最適化勉強会#1

Page 2: 条件分岐とcmovとmaxps

内容 内容

自己紹介

最適化の心得

プロファイル

ベンチマーク環境

条件分岐は重たい

条件分岐を減らす古典的なテクニック

cmovを使う

条件分岐は重たい?

SIMD命令を使う

maxps, pmaxsd /24 2

Page 3: 条件分岐とcmovとmaxps

自己紹介 自己紹介

光成滋生(サイボウズ・ラボ)

姑息な最適化が大好き

以前はコーデック系の趣味・仕事をやっていた

最近は仕事のために機械学習を勉強中

PRMLあんちょこ公開中(https://github.com/herumi/prml/)

blog : http://homepage1.nifty.com/herumi/

mail : [email protected]

twitter : @herumi

/24 3

Page 4: 条件分岐とcmovとmaxps

最適化の心得 最適化の心得

プログラム最適化の第一法則

最適化するな

プログラム最適化の第二法則

まだするな

そんなことはわかってる

人たちが集まってるよね…

/24 4

Page 5: 条件分岐とcmovとmaxps

プロファイル プロファイル

プロファイラ

関数組み込みタイプ

プログラム自体に組み込まれる

それ自体がプログラムに影響を与える

回数は正確

gprofやDevPartner, Vtune

タイミング割り込みによる集計タイプ

割り込みでその瞬間のeipを取得し集計

プログラムに影響を(殆ど)与えない

回数は不正確,時間は概ね正確

CodeAnalyst, perf, VTune

推測するな,実測せよ(もちろん仮説と検証も大事)

/24 5

Page 6: 条件分岐とcmovとmaxps

rdtsc rdtsc

nsecレベルの時間取得が可能

時刻取得システムコールはそれほど分解能が高くない

HPET(High Precision Event Timer)が使えるなら多少はましだが

最近のCPUの周波数が可変だと多少困る

マルチコアだと多少困る

プロセッサを判別できるrdtscpというのもある

シリアライズされてない,それ自体が若干時間を食う

が,お手軽なのがよい(以下はXbyakでの例)

/24 6

Xbyak::util::Clock clk; clk.begin(); 計測したい部分 clk.end(); printf("%d¥n", clk.getClock());

Page 7: 条件分岐とcmovとmaxps

ベンチマーク環境 ベンチマーク環境

今回のコード

https://github.com/herumi/opti/

要Xbyak http://homepage1.nifty.com/herumi/soft/xbyak.html

テスト環境

Xeon X5650 2.67GHz + Linux 2.6.32 + gcc 4.6.0

Core i7-2600 CPU 3.40GHz + Linux 2.6.35 + gcc 4.4.5

ただしWindows 7上のVMware上

Core i7-2600 Cpu 3.40GHz + Windows7(64bit) + VC2010

/24 7

Page 8: 条件分岐とcmovとmaxps

古典的なテクニック 古典的なテクニック

範囲チェック

分岐が2回

次のようにすると分岐は1回

後半の文字列で再度登場 /24 8

unsigned int a, b, x; if (a <= x && x <= b) { ... }

cmp x, a jb #elese cmp x, b ja #else ...

if ((x – a) <= (b – a)) { ... }

Page 9: 条件分岐とcmovとmaxps

条件分岐は重たい 条件分岐は重たい

不要な分岐を除去せよ

Intel64 and IA-32 Architectures Optimization Reference Manual コーディング規則1

CPUのパイプラインを乱す

大抵10段以上ある

スループットがあがらない

/24 9

Page 10: 条件分岐とcmovとmaxps

例題1. countMax 例題1. countMax

次の関数を最適化せよ

分岐命令をcmovやsetgに置き換えよ

(注意)x[i], y[i] >= 0を仮定する

/24 10

size_t countMax_C(const int *x, const int *y, size_t n) { size_t count = 0; for (size_t i = 0; i < n; i++) { if (x[i] > y[i]) count++; } return count; }

Page 11: 条件分岐とcmovとmaxps

jmp命令を使う jmp命令を使う

jmp-cmov.cpp抜粋

1ループあたり10~12clk

重たい

/24 11

L(".lp"); mov(t, ptr [x + n * 4]); // x[i]を読んで cmp(t, ptr [y + n * 4]); // y[i]と比較して jle(".skip"); // x[i] <= y[i]ならskip add(a, 1); // count++ L(".skip"); add(n, 1); jne(".lp");

Page 12: 条件分岐とcmovとmaxps

setg命令を使う setg命令を使う

setXXは条件が満たされたときに8bitレジスタを1に設定する(外れたら0)命令

8bitなのが結構使いにくい

jmp-cmov.cpp抜粋

1ループあたり2~2.5clk! /24 12

L(".lp"); mov(edx, ptr [x + n * 4]); // x[i]を読んで cmp(edx, ptr [y + n * 4]); // y[i]と比較して setg(dl); // dl = x[i] > y[i] ? 1 : 0 movzx(edx, dl); // 上位24bitを0クリア add(eax, edx); // countに加算 add(n, 1); jne(".lp");

Page 13: 条件分岐とcmovとmaxps

adc命令を使う adc命令を使う

adcはcarryつきadd命令

cmpしたときにcarry = y[i] < x[i] ? 1 : 0

eax = eax + 0 + carry = eax + (x[i] > y[i] ? 1 : 0);

(注意)符号無しの計算になるのでx[i], y[i] >= 0を仮定

/24 13

L(".lp"); mov(edx, ptr [y + n * 4]); // y[i]を読んで cmp(edx, ptr [x + n * 4]); // x[i]と比較して adc(eax, 0); // ??? add(n, 1); jne(".lp");

Page 14: 条件分岐とcmovとmaxps

条件分岐は重たい? 条件分岐は重たい?

計ってみると1ループあたり1clk程度?

実は条件分岐の予測がはずれたとき重たい

最近のCPUの条件分岐予測機構は優秀なので結構当たる

あたった場合のコストはかなり低い

/24 14

L("@@"); sub(ecx, 1); jnz("@b");

Page 15: 条件分岐とcmovとmaxps

条件分岐は重たい? 条件分岐は重たい?

与えられた配列の最大値の取得(cmov vs. jge)

/24 15

int getMax_C(const int *x, size_t n) { int max = x[0]; for (size_t i = 1; i < n; i++) { if (x[i] > max) max = x[i]; } return max; }

// a = max cmp(a, ptr [x + n * 4]); // if (a > x[i]) a = x[i] cmovl(a, ptr [x + n * 4]);

// a = max cmp(a, ptr [x + n * 4]); jge(".skip"); mov(a, ptr [x + n * 4]); L(".skip");

Page 16: 条件分岐とcmovとmaxps

ベンチマーク ベンチマーク

ランダムな値を入れた配列に対して cmovが2.7clkなのに対してjmpは1.8clk!

jmpの方が速い

ランダムな配列の最大値はどこか途中で出てくると それ以降は必ずそれより小さい

分岐予測がヒットする

最適化マニュアルを見直す(コーディング規則2)

setcc, cmovを使って分岐を排除せよ. ただし,予測可能な分岐に対しては排除してはいけない

さて,配列の最大値で最後まで予測できないのは不規則に単調増加しているとき.そんなケースは多くなさそう

/24 16

Page 17: 条件分岐とcmovとmaxps

大抵はjmpが速そうだ(@i7)

ちなみに最初のcountMaxのベンチマーク

x[i] > y[i]となる割合

一番予測できないときjmpの数値が非常に悪い

もう少しベンチマーク もう少しベンチマーク

ランダム 最初が一番大きい 単調増加 時々増加

jmp 1.859 1.823 1.836 3.637

cmov 2.741 2.718 2.747 2.751

/24 17

rate 0.00 0.25 0.50 0.75 1.00

jmp 1.863 7.827 12.747 9.276 1.854

setg 2.054 2.043 2.052 2.026 2.054

adc 1.842 1.838 1.849 1.828 1.822

Page 18: 条件分岐とcmovとmaxps

分岐点はどこか 分岐点はどこか

概ね経験則で分岐予測がヒットしたとき<1clk 外れたとき20clk

先程もrate=50%のときが12clk程度

cmovなどの条件分岐排除命令は数clk

ざっくり,分岐予測が1/10しか外れないなら互角かも

データがどういう性質を持っているかをよく見極めることが大切

/24 18

Page 19: 条件分岐とcmovとmaxps

SIMDを使ってみる SIMDを使ってみる

maxps = max(x[i], y[i]) for i = 0..3 を求める

SIMD凄い! /24 19

// xm0 = [x[3]:x[2]:x[1]:x[0] L("@@"); maxps(xm0, ptr [x + n * 4]); // update xm0 add(n, 4); jnz("@b"); // ループが終わったらxm0のうちの一番大きいものを求める

i7 ランダム 最初が一番大きい 単調増加 時々増加

jmp 1.859 1.823 1.836 3.637

cmov 2.741 2.718 2.747 2.751

maxps 0.686 0.689 0.677 0.687

Page 20: 条件分岐とcmovとmaxps

実はちょっとした落とし穴 実はちょっとした落とし穴

Xeon, Core2Duoなどのプロセッサでは劇遅

Xeonだと30clk以上!

PentiumDだと速かったりする

maxpsは浮動小数点数(float)に対する命令

普通の整数値はfloatとしてみると非正規化数になっちゃうことも多い

ペナルティを食らう

float x[N]に対する先程の命令ではXeonだと0.648clk

PentiumDの頃は型にうるさく無かった?

整数に対するmax命令はpmaxsd

SSE4.1にしか無いがmaxpsよりは速い /24 20

Page 21: 条件分岐とcmovとmaxps

ベンチマーク ベンチマーク

i7 ランダム 最初が一番大きい 単調増加 時々増加

jmp 1.859 1.823 1.836 3.637

cmov 2.741 2.718 2.747 2.751

maxps 0.686 0.689 0.677 0.687

pmaxsd 0.276 0.279 0.287 0.273

/24 21

Xeon ランダム 最初が一番大きい 単調増加 時々増加

jmp 3.414 1.745 1.746 3.481

cmov 5.008 2.613 2.612 2.653

maxps 30.741 26.581 26.587 26.547

pmaxsd 0.437 0.437 0.437 0.437

http://agner.org/optimize/blog/read.php?i=142

i7は非正規数の扱いが高速になった

Page 22: 条件分岐とcmovとmaxps

maxssよりjmpがよいレアケース maxssよりjmpがよいレアケース

floatに対する高速なexp, logの実装fmath.hpp

https://github.com/herumi/fmath/

gccの数倍から一桁以上速い

exp(x)はxがある値A以上で∞, -A以下で0とみなす

その範囲でクリッピングしたい

/24 22

Xeon exp expx4 log logx4

gcc 4.6 565.9 2265.7 54.1 222.7

fmath 15.2 35.4 12.9 32.3

minss(x, A); maxss(x, -A); ...

Page 23: 条件分岐とcmovとmaxps

maxssよりjmpがよいレアケース maxssよりjmpがよいレアケース

maxss/minssのレイテンシーは3clkぐらい

依存関係が高いので隠蔽しにくい

通常expが0になったり∞になったりする値は扱わない

範囲外はレアケースとして追い出す

これで15.9clk → 14.2clkと少し速くなった

/24 23

minps(x, A); maxps(x, -A); ...

movd(edx, x); and(edx, 0x7fffffff); ... // メインの作業 cmp(edx, A); // 整数として比較して jg(".overflow"); L(".overflow"); // レアケースの場合は minss(x, A); // やり直し maxss(x, -A); jmp(".retry");

Page 24: 条件分岐とcmovとmaxps

まとめ まとめ

条件分岐を排除せよ

ただし分岐予測ができないときのみ

意外とCPUの分岐予測に任せた方がよいこともある

cmov, setgは起こらなかったときのコストを薄く払う

可能ならSIMD命令を使う

トリッキーな非SIMDよりも簡単に速くできる(かも)

整数演算と浮動小数演算の命令に注意する

同じ内容でも異なる命令が割り当てられている

andpsとpand, movdqaとmovapsなど

SIMDよりもjmpがよいときもあることはある

いろんなパターンを考えよう /24 24