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. 1.の結果が残っている
  2. 0クリアされてる
  3. 未定義

まず、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には興味無いです」と、いうことを示すことができ、無駄なレジスタの保存/復元を無くせる、というわけである。


使いかたとしては、

  1. AVX命令を使う
  2. (必要なら)ymmレジスタの上位128bitを保存する
  3. vzeroupperを呼ぶ
  4. レガシーSSE命令を呼ぶ(かもしれない)ライブラリ関数を呼ぶ

と、いうような感じになる。