gcc-4.x の SIMD 命令サポート その2 †新しいマシンを買い,Linux マシンが Celeron D にグレードアップしてしまいました. というわけで,今回は SSE/SSE2 について. SSE レジスタ †SSE では 128 ビット長の専用レジスタを 8 本新設しています. つまり,前回の 3DNow! とは違い,x87 演算と分離する必要はありません. で,レジスタの中身ですが,SSE レジスタ 1 本に
を格納し,SIMD 演算を行うことができます. 参考文献 †参考となる書籍など.
おおまかな枠組み †前回と一緒で
です. プログラム例 †前回と同様にベンチマークを兼ねてプログラムを作ってみました. お題も前回と同じく積和演算です. 型宣言 †まずは SSE での「単精度 x 4 本」の構成の変数の型宣言から. typedef float v4sf __attribute__ ((vector_size(16))) __attribute__((aligned(16))); 前回と違っておしりに __attribute__((aligned(16))) なんて属性がついてますね. これは
という属性です. これは,実は SSE レジスタのロード・ストア時においてこの様な制限があるために,このような属性を追加しています. この制限を無視するとどうなるかというと,Segmentation fault が発生します. ARM や MIPS などの変数のアラインメントと似たような感じです. ここで 1 つ注意.この aligned(16) 属性は v4sf a; のような変数の宣言では有効に機能しますが v4sf *a = malloc ( sizeof(v4sf) ); のような動的メモリ割り当てではうまく働きません.また float a[2]; *(v4sf *)&a[0] = b; のようなキャストもだめです. つまり,既に割り当てられている領域を「16 で割り切れるアドレスに配置換え」する力はありません. ついでに「倍精度 x 2」の型宣言. typedef double v2df __attribute__ ((vector_size(16))) __attribute__((aligned(16))); 制限・注意は v4sf 型と一緒です. SSE/SSE2 命令 †まずは v4sf 型について. これらは SSE 命令セットに属します.
v2df 型について.これらは SSE2 命令セットです.
これらの明示的なビルトイン関数以外にも例えば v2sf a = { 1.0, 2.0, 3.0, 4.0 }; のような代入文でも SSE 命令が使用されます. つまり,単なる代入においても右辺と左辺の変数領域は 16 で割り切れるアドレスでなければなりません. うっかりすると忘れてしまうので注意しましょう. ベンチマーク環境 †結果に先立って,ベンチマーク環境について書いときます. $ cat /proc/cpuinfo processor : 0 vendor_id : GenuineIntel cpu family : 15 model : 4 model name : Intel(R) Celeron(R) CPU 2.66GHz stepping : 9 cpu MHz : 2667.096 cache size : 256 KB fdiv_bug : no hlt_bug : no f00f_bug : no coma_bug : no fpu : yes fpu_exception : yes cpuid level : 5 wp : yes flags : fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush dts \ acpi mmx fxsr sse sse2 ss ht tm pbe lm constant_tsc pebs bts pni monitor ds_cpl tm2 cid cx16 xtpr \ lahf_lm bogomips : 5340.17 clflush size : 64 power management: ベンチマーク結果 †まずは最適化オプションなしでこんな感じ. $ gcc -msse2 benchmark.c $ ./a.out SSE double: 9.620000 3998000.000000 norm double: 20.890000 3998000.000000 SSE float: 4.910000 3998000.000000 norm float: 20.560000 3998000.000000 中央のカラムが演算にかかった CPU 時間で,単位は秒です. 最適化オプションを付けてみましょう. $ gcc -msse2 -O2 benchmark.c $ ./a.out SSE double: 2.680000 3998000.000000 norm double: 4.910000 3998000.000000 SSE float: 1.070000 3998000.000000 norm float: 4.760000 3998000.000000 かなり違いますね. ループ展開もしてみましょう. $ gcc -msse2 -O2 -funroll-loops benchmark.c imai@JR0BAK:~/progs/sse$ ./a.out SSE double: 2.380000 3998000.000000 norm double: 4.850000 3998000.000000 SSE float: 1.060000 3998000.000000 norm float: 4.670000 3998000.000000 ちょっぴり速くなりましたが,3DNow! のときのように劇的な効果はないようです. ループ展開したことで SSE/SSE2 の演算では多くの SSE レジスタを使用するコードが生成されています. にもかかわらずあまり速度が上がってないのは,実は SSE/SSE2 でも演算ユニットは 1 セットしかないのかもしれません. 最後の例で SSE/SSE2 と x87 の速度を比較すると,SSE/SSE2 の SIMD 演算を用いたことにより
のスピードアップが図られたことになります. |