vzeroupper(_mm256_zeroupper)とは何か
(注意 : コメントで指摘いただきましたが、以下の内容は完全にデマでなので信用しないでください。すいません)
(以下、256bit演算する命令を「AVX命令」、VEXの付いてない、既存の128bit演算を「レガシーSSE命令」とする)
レガシーなSSE命令とAVXを混ぜたときのペナルティーを無くす命令。
日本語の解説は今のところ無いみたいなので、言語障壁を活用したブログエントリでも書くか。(簡単な解説を2chのどっかで見た気がするが)
詳細は、http://www.intel.com/products/processor/manuals/ のoptimization manualの11.3。
AVXの256bit演算で使うレジスタは、SSEとかの128bit演算で使うレジスタと共用されていて、ymmの下128bitがxmmレジスタになる。
さて、このときに、256bit演算と、128bit演算を混ぜるとどうなるか、という問題がある。
1: vaddps ymm0, ymm1, ymm2 # ymm0 = ymm1 + ymm2 2: addps xmm0, xmm0 # xmm0 = xmm0 + xmm0 3: vaddps ymm1, ymm0, ymm0 # ymm1 = ymm0 + ymm0
3: のときにymm0レジスタの状態はどうなっているべきだろうか。
- 1.の結果が残っている
- 0クリアされてる
- 未定義
まず、2, 3 は、問題がある。x86-32 のcalling convention(呼び出し規約)のレジスタ保存のルールとしては、xmm0-7のレジスタは、callee save(レジスタを使う関数がなんとかする)と、なっている。
「128bit演算はymmレジスタの上位128bitを破壊する」、というルールになっていた場合、既存のxmmレジスタを使うライブラリ関数等を呼んだときに、このcalling conventionが破壊されてしまうことになる。ライブラリ関数がDLLの中にあった場合には、関数がxmmレジスタを破壊するかどうか、は、コンパイラにはわからないので、「ひょっとしたら、ymmの上位は壊されるかもしれんし、壊されないかもしれん」という状態になる。まあ、それで、caller-save(関数呼び出し側で保存する)というcalling conventionにするというのもありかもしれんが、それは無駄が多い(多いか?あとで真面目に考える)。
さて、では、1 は、どうか、というと、これも問題がある。
今時のOut-of-Orderなプロセッサは、レジスタリネーミングして、WAW依存(わぅー依存)を解決してるわけだが、上位128bitを保存する、というルールにしてると、このWAW依存が解決できない。
例えば、↓こういうのだと、
movdqu xmm0, [eax] adddqu [edx], xmm0 movdqu xmm0, [eax + 16] adddqu [edx + 16], xmm0
上ふたつの命令と、下ふたつの命令は、同時に実行できるはずだが、「上位128bitを保存しないといけない」というルールがあると、ymm0の上位128bitを保存するために実行順序が決まってしまうため(TODO:解説が怪しい)、並列実行ができなくなってしまう。
と、いうわけで、「上位128bitは保存してもアレだし、保存しないでもアレだなぁ…」と、いう感じであった。
で、どうするか、だが、レガシSSE命令を使ってるライブラリ関数は、昔から存在するライブラリだと考えれば、ほぼ間違いなく、AVX命令は使ってないとみてよいだろう。
この点を利用して、今の実装は、「今どっちのレジスタを使っているか」という状態を記録しておいて、状態の切り変え時に、ymmの上位128bitを保存したり復元したりしている。
- 最初は'Clean'
- 'Clean'な状態でレガシSSEを使っている間は'Clean'
- 'Clean'な状態でAVXを使うと、'Modified/Unsaved(M/U)'に
- M/Uな状態でSSEを使うと、上位128bitを保存して、'Saved'に
- Savedな状態でAVXを上位128bitを復元して'M/U'に
と、いうような感じ(あ、XSAVEとXRSTORをあんま理解してないからちゃんと書けないな。すいません)
この時に、上位128bitを復元したり保存したりするのだが、これが結構ペナルティあるのだった(適当に測ったら150[clk]ぐらいあったような。TODO:あとでちゃんと測る)
このペナルティをなんとかするのが、vzeroupperである。
vzeroupperは、ymmレジスタの上位128bitをゼロクリアして、ymmレジスタの状態を'Clean'にする命令である。
つまり、vzeroupperは、プロセッサに対して、「もうymmレジスタの上位128bitには興味無いです」と、いうことを示すことができ、無駄なレジスタの保存/復元を無くせる、というわけである。
使いかたとしては、
- AVX命令を使う
- (必要なら)ymmレジスタの上位128bitを保存する
- vzeroupperを呼ぶ
- レガシーSSE命令を呼ぶ(かもしれない)ライブラリ関数を呼ぶ
と、いうような感じになる。