gcc-4.x での SIMD 命令サポート

「gcc 4 では MMX, 3DNow!, SSE などの SIMD 命令がサポートされた」「-msse オプションを付けてコンパイルすると SSE 命令が有効になる」なんて話を聞いたことがある人も多いと思います. が,これだけではマトモに SIMD 命令を生成してくれません

というわけで,そこらへんの話を調べた範囲で書いてみようと思います.

情報源

gcc の info がメインです. あと,各命令セットのドキュメントも用意しときましょう.

おおまかな枠組み

  1. SIMD のレジスタにあった型の変数を定義して
  2. ビルトイン関数で演算を記述
  3. -mXXX オプションを付けてコンパイル
    • -mmmx, -m3dnow, -msse, -msse2 など
  • 枠組みとしては汎用
    • x86 だけでなく,MIPS や XScale の wireless MMX もこの方法でサポートしてるそうです

ま,こんなところです.

3DNow! の例

何でいまどき 3DNow! かと言うと,今使ってる PC が Eden だったりするので. まぁ,SSE とかでも同じような要領でいけるはずです.

今回は,単精度浮動小数点の積和演算のプログラムを組んでみようと思います.

3DNow! 命令セット

マニュアルは AMD の web からダウンロードできます.

3DNow! 命令の作法

3DNow! では,MMX と同様に 80387 浮動小数点レジスタを利用します. が,好き勝手に 3DNow! 命令を使うと,80387 が混乱してしまいます.

というわけで,3DNow! 命令を使うセクションの前後には femms 命令でレジスタを初期化する必要があります. つまり

80387演算
femms
3DNow! 演算(80387 利用不可)
…
femms
80387演算

としなくてはなりません.

SSE 以降では専用レジスタがあるので,このような切り替えの作法は不要だと思います.

このような型を宣言します.

typedef float v2sf __attribute__ ((vector_size(8)));

float が 2 つくっついた型で,レジスタ 1 つ分に相当します.

宣言と代入

要するに typedef された型なので,宣言自体は

v2sf a;

でできます. 宣言と同時に初期値を代入する場合は

v2sf a = { 1.0, 2.0 };

と2つの実数を {} で囲んで渡します.

代入する時は

a = (v2sf){ 1.0, 2.0 };

とします.

値の取り出し

…どーやるんだ,これ? info を見ても書いてないようです.

float f;
f = a[1];

ではコンパイラにはねられます. が,gdb 上では有効だったりします.

しかたがないので,とりあえず

float f[2];
*(v2sf *)f = a;

とかやって,強引に取り出しています.

ちなみに

float *f = (float *)&a;

でも取り出すことはできます. この場合,「a のアドレス」を参照してしまっているので,a を register v2sf 型で宣言できなくなってしまいます. つまり,最適化の妨げになる可能性があるので,やらないほうがいいでしょう.

演算

gcc 上では __builtin_… 命令を使います.

今回使用したのは

v2sf __builtin_ia32_pfadd ( v2sf a, v2sf b )
a + b
v2sf __builtin_ia32_pfmul ( v2sf a, v2sf b )
a x b
v2sf __builtin_ia32_pfacc ( v2sf a, v2sf b )
{ a(上位) + a(下位), b(上位) + b(下位) }
void __builtin_ia32_femms()
80387 レジスタの初期化

です.

プログラム

ただ計算するだけでもつまんないので,80387 演算との速度を比較できるようにしてみました.

実行結果

コンパイルオプションを色々変えて試してみました. 環境は Eden 800MHz 上の Linux です. まずは,素で.

sh-3.2$ gcc -m3dnow benchmark.c 
sh-3.2$ ./a.out 
3DNow!:         11.700000       3998000.000000
387 double:     11.180000       3998000.000000
387 float:      11.160000       3998000.000000

2番目のカラムが実行時間,一番右が実行結果です. 3DNow!,全然速くないですね.それでは,最適化レベルをあげていってみましょう. まずは O1

sh-3.2$ gcc -O1 -m3dnow benchmark.c 
sh-3.2$ ./a.out 
3DNow!:         1.270000        3998000.000000
387 double:     3.070000        3998000.000000
387 float:      2.540000        3998000.000000

さらに O2

sh-3.2$ gcc -O2 -m3dnow benchmark.c 
sh-3.2$ ./a.out 
3DNow!:         1.280000        3998000.000000
387 double:     3.050000        3998000.000000
387 float:      2.570000        3998000.000000

O1 と変化無いですね. ループを展開してみましょう.

sh-3.2$ gcc -funroll-loops -O2 -m3dnow benchmark.c 
sh-3.2$ ./a.out 
3DNow!:         0.890000        3998000.000000
387 double:     3.050000        3998000.000000
387 float:      2.540000        3998000.000000

こいつは効果があるようです. 80387 命令比で 2.8 倍の速度で計算できてます.


トップ   編集 凍結 差分 バックアップ 添付 複製 名前変更 リロード   新規 一覧 単語検索 最終更新   ヘルプ   最終更新のRSS
Last-modified: 2008-09-03 (水) 00:36:25 (5738d)