今のパソコンの CPU にはほぼ SIMD 命令の SSE がついています。
単精度の浮動小数であれば 4要素同時に演算することが可能で、
ちょうど shader のベクトルレジスタに似ています。
ところが SSE は shader とは違い、2オペランドの破壊型演算命令だったり
各要素をばらばらにアクセスするのが困難だったり、
水平演算が苦手だったりと意外にとっつきにくいものでした。
また演算時のスループットも当初 2以上で、ただただその命令に
置き換えれば速くなるとは限りませんでした。
特徴や使いどころの傾向をつかむまでは苦労させられます。
CPU の進化とともに SSE も強化されており、SSE2、SSE3~ 等と
命令も実行効率も進化しています。
・SSE Pentium3~ 128bit xmm0~7 レジスタ追加、float×4 の演算 等
・SSE2 Pentium4~ double ×2、xmm 整数演算 等
・SSE3 Pentium4~ 実数用水平演算命令など
・SSSE3 Core2~ xmm 整数用水平演算命令など
・SSE4 (Penryn)~ 最大最小検索、DotProduct、bit数カウント、CRC32命令など
最近あらためて、これらの命令などをいろいろ調べていました。
これはただのメモです。
VisualStudio は FPU の代わりに SSE を使って浮動小数点演算を行う
ことができます。2005 の場合コンパイルオプション /arch:SSE or /arch:SSE2
の指定で FPU 命令の置き換えになります。また演算時の精度を /fp で
指定します。これは必要に応じて SSE/SSE2 命令を使ってくれるので便利です。
たとえば /arch:SSE2 /fp:fast 時、float の int へのキャストは
cvttss2si 命令になりました。SSE 指定がない場合は _ftol2 の呼び出し
になっています。
int castInt( float a )
{
return (int)a;
}
// ↓
cvttss2si eax, DWORD PTR _a$[esp-4]
Windows の x64 では FPU は使われずに最初から SSE を使った演算のみの
サポートとなるようです。(32bit 互換モードを除く) なので今後ますます
SSE/SSE2 が多用されていくことになるでしょう。
float から _int64 へのキャストも x64 の場合は cvttss2si 命令ですが、
32bit では実行できないので _ftol2 の呼び出しになっていました。
コンパイル時のオプション /fp:fast のあるなしは演算精度に影響します。
・MSDN /fp (浮動小数点の動作の指定)
例えば全部 float の演算の場合、/fp:fast 無し (/fp:precise) では
float va, vb, vz;
float xs= va * vb + vz;
movss xmm0, DWORD PTR vb
movss xmm1, DWORD PTR va
cvtps2pd xmm0, xmm0 ; double ← float
cvtps2pd xmm1, xmm1 ; double ← float
mulsd xmm0, xmm1
movss xmm1, DWORD PTR vz
cvtps2pd xmm1, xmm1 ; double ← float
addsd xmm0, xmm1
cvtpd2ps xmm0, xmm0 ; float ← double
演算途中の値が倍精度で保たれるよう事前に変換が発生しています。
これを /fp:fast でコンパイルすると単精度のまま演算が行われている
ことがわかります。
movss xmm0, DWORD PTR vb
mulss xmm0, DWORD PTR va
addss xmm0, DWORD PTR vz
さらに
double xd= (double)va * (double)vb + (double)vz;
でも結果は上記と全く同じコードで、演算後に cvtps2pd で変換していました。
予想よりもずっと大胆な最適化なので /fp:fast を使うときは注意が必要でしょう。
ここで少々不思議なのは movss したスカラー値を倍精度変換するために
cvtps2pd が使われていることです。パックしてから変換を最適化している
わけでもないので、本来は cvtss2sd で十分だと思われます。
・intel 日本語技術資料のダウンロード
ここからダウンロードできる「ia32命令 レイテンシトスループット」や
「IA-32 インテルR アーキテクチャー最適化リファレンス・マニュアル」を読むと
ia32.pdf Pentium4
・cvtps2pd 10/4 (レイテンシ/スループット)
・cvtss2sd 14/3
ia32_final.pdf Pentium4
・cvtps2pd 2/2
・cvtss2sd 8/2
ia32_final.pdf Pentium-M
・cvtps2pd 2+1/3
・cvtss2sd 2/2
Pentium4 では cvtps2pd よりも cvtss2sd 命令のほうがレイテンシがかなり
大きいことがわかります。ドキュメントによって Pentium4 の値が異なるのは
おそらく cpu core の世代の違いだと思われます。
またドキュメントにはもう少し興味深いこともかかれています。
movsd xmm, xmm 等レジスタの 64bit 分化しか更新せずに上位 64bit が保持
される命令の場合は、上位 bit の結果に依存関係が発生してしまうそうです。
そのため実行の並列化が阻害されます。
レジスタを 128bit 分すべて書き換える命令の方が依存関係が発生しないため
レジスタのリネーミングも可能で好ましいとのことです。なるほど。