* gcc-4.x での SIMD 命令サポート [#a47d7162] 「gcc 4 では MMX, 3DNow!, SSE などの SIMD 命令がサポートされた」「-msse オプションを付けてコンパイルすると SSE 命令が有効になる」なんて話を聞いたことがある人も多いと思います. が,''これだけではマトモに SIMD 命令を生成してくれません''. というわけで,そこらへんの話を調べた範囲で書いてみようと思います. ** 情報源 [#iee772ca] gcc の info がメインです. あと,各命令セットのドキュメントも用意しときましょう. ** おおまかな枠組み [#b5f08def] + SIMD のレジスタにあった型の変数を定義して + ビルトイン関数で演算を記述 + -mXXX オプションを付けてコンパイル -- -mmmx, -m3dnow, -msse, -msse2 など - 枠組みとしては汎用 -- x86 だけでなく,MIPS や XScale の wireless MMX もこの方法でサポートしてるそうです ま,こんなところです. ** 3DNow! の例 [#j5b7771f] 何でいまどき 3DNow! かと言うと,今使ってる PC が Eden だったりするので. まぁ,SSE とかでも同じような要領でいけるはずです. 今回は,単精度浮動小数点の積和演算のプログラムを組んでみようと思います. ** 3DNow! 命令セット [#te8da23e] マニュアルは [http://www.amd.com/us-en/assets/content_type/white_papers_and_tech_docs/21928.pdf AMD の web] からダウンロードできます. ** 3DNow! 命令の作法 [#b71919ff] 3DNow! では,MMX と同様に 80387 浮動小数点レジスタを利用します. が,好き勝手に 3DNow! 命令を使うと,80387 が混乱してしまいます. というわけで,3DNow! 命令を使うセクションの前後には femms 命令でレジスタを初期化する必要があります. つまり 80387演算 femms 3DNow! 演算(80387 利用不可) … femms 80387演算 としなくてはなりません. SSE 以降では専用レジスタがあるので,このような切り替えの作法は不要だと思います. ** 型 [#i9e7863b] このような型を宣言します. typedef float v2sf __attribute__ ((vector_size(8))); float が 2 つくっついた型で,レジスタ 1 つ分に相当します. ** 宣言と代入 [#w0b53f28] 要するに typedef された型なので,宣言自体は v2sf a; でできます. 宣言と同時に初期値を代入する場合は v2sf a = { 1.0, 2.0 }; と2つの実数を {} で囲んで渡します. 代入する時は a = (v2sf){ 1.0, 2.0 }; とします. ** 値の取り出し [#rd13f4d1] …どーやるんだ,これ? info を見ても書いてないようです. float f; f = a[1]; ではコンパイラにはねられます. が,gdb 上では有効だったりします. しかたがないので,とりあえず float f[2]; *(v2sf *)f = a; とかやって,強引に取り出しています. ちなみに float *f = (float *)&a; でも取り出すことはできます. この場合,「a のアドレス」を参照してしまっているので,a を register v2sf 型で宣言できなくなってしまいます. つまり,最適化の妨げになる可能性があるので,やらないほうがいいでしょう. ** 演算 [#m803056b] 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 レジスタの初期化 です. ** プログラム [#ba3074a9] #ref(benchmark.c) です. ただ計算するだけでもつまんないので,80387 演算との速度を比較できるようにしてみました. ** 実行結果 [#a88e0aaf] コンパイルオプションを色々変えて試してみました. 環境は 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 倍の速度で計算できてます.