2009年09月10日
gccでNEONのSIMD命令を生成させる方法
この記事ではgcc 4.4.0を使用しています。もっと新しいgccの場合はこちら。
2013年06月27日 ARMのNEONのSIMD命令をgccのオートベクタライズの最適化で使う方法
AndroidのSDKのgoldfishのCPUをcortex-A8に置き換えてNEONのSIMD命令を試す(その2) のときにはarm_neon.hに定義されているintrinsicsを使ってNEONのSIMD命令を生成させました。この方法だとNEONの命令について詳細を知っていなければなりませんし、なによりもそのプログラムがNEONに依存したものになってしまいます。
今回はコンパイラの最適化の機能を使ってNEONのSIMD命令を生成させるコツを紹介します。
以前にSIMD演算のサンプルとしてこんなプログラムを書きました。
void vmla(float* a, float* x, float* y, float* z)
{
a[0] = x[0] * y[0] + z[0];
a[1] = x[1] * y[1] + z[1];
a[2] = x[2] * y[2] + z[2];
a[3] = x[3] * y[3] + z[3];
}
これだと実際にはNEONのSIMD命令は生成されませんが、これをちょっと書き直すとNEONのSIMD命令が生成されるようになります。
ポイントは
- 配列をループで使うこと。
- その配列のポインタに __restrict の属性がついていること。
のようです。
__restrict はそのポインタが他の変数と重なっていないことをコンパイラに知らせるためのものです。
これらを修正したのが以下のプログラムです。
void vmla2(float* __restrict a, float* __restrict x, float* __restrict y, float* __restrict z)
{
int i;
for (i=0; i< 4; i++) {
a[i] = x[i] * y[i] + z[i];
}
}
これをneon0.c というファイルに格納します。
今回コンパイラはgcc 4.4.0を使っています。
コンパイルオプションは以下のものをつけます。
arm-eabi-gcc -O2 -march=armv7-a -ftree-vectorize -mhard-float -mfloat-abi=softfp -mfpu=neon -mvectorize-with-neon-quad -S neon0.c
生成されたコードは以下のとおり。
.align 2
.global vmla2
.type vmla2, %function
vmla2:
@ args = 0, pretend = 0, frame = 0
@ frame_needed = 0, uses_anonymous_args = 0
@ link register save eliminated.
orr ip, r1, r0
orr ip, r2, ip
orr ip, r3, ip
tst ip, #15
str r4, [sp, #-4]!
bne .L4
vldmia r1, {d18-d19}
vldmia r2, {d16-d17}
vmul.f32 q8, q9, q8
vldmia r3, {d18-d19}
vadd.f32 q8, q8, q9
vstmia r0, {d16-d17}
.L7:
ldmfd sp!, {r4}
bx lr
.L4:
mov ip, #0
.L6:
add r4, r1, ip
flds s13, [r4, #0]
add r4, r2, ip
flds s14, [r4, #0]
add r4, r3, ip
flds s15, [r4, #0]
fmacs s15, s13, s14
fmrs r4, s15
str r4, [r0, ip] @ float
add ip, ip, #4
cmp ip, #16
bne .L6
b .L7
.size vmla2, .-vmla2
NEONのSIMD命令が生成されましたが、積和命令でなく、乗算と加算に分かれました。このプログラムでは直前にメモリからロードしているので、そのロードの遅延を考慮するとこの方が速いのかもしれません。また、3つのオペランドのアライメントをチェックしていて3つとも16バイト境界にあるときのみNEON命令を使うようになっています。興味深いです。
このサイトを参考にさせていただきました。
トラックバックURL
トラックバック一覧
コメント一覧
いつも興味深く拝見させていただいています。
メインの話題とずれていて恐縮ですが
> -mhard-float -mfloat-abi=softfp
の "-mhard-float" の指定は不要ではないでしょうか?
gcc-4.3のmanを参照すると、
> -mfloat-abi=name
> Specifies which ABI to use for floating point values. Permissible values are: soft, softfp and hard.
>
> soft and hard are equivalent to -msoft-float and -mhard-float respectively.
と書いてあるので、"-mhard-float" は "-mfloat-abi=hard" と同じだというように理解しています。
-mhard-floatは (Androidでのデフォルトである) -msoft-floatとの対比の意味で明示的に指定しています。
ついでの話をすると、
-mhard-floatをつけると暗黙のうちに -mfloat-abi=hard になりますが、現状ARMのコンパイラではfloatのABIでhardはサポートされていません。なので、必ず -mhard-float を指定したら -mfloat-abi=softfp も指定する必要があります。
次にでてくるgcc 4.5 ではARMでの-mfloat-api=hardがサポートされるようです。
というのは、以下のように "-mhard-float" をつけてコンパイルするとエラーになることからも、分かります。
$ arm-linux-gnueabi-gcc -mhard-float sample.c
sample.c:1: sorry, unimplemented: -mfloat-abi=hard and VFP
> なので、必ず -mhard-float を指定したら -mfloat-abi=softfp も指定する必要があります。
というのが、分からないです。
以下のように、"-mfloat-abi=softfp" と "-mhard-float" の順番を入れ替えて、"-mhard-float" を後ろに持ってくると、"-mhard-float"だけを指定したときと同じエラーになります。
$ arm-linux-gnueabi-gcc -mhard-float -mfloat-abi=softfp sample.c
(エラーなし)
$ arm-linux-gnueabi-gcc -mfloat-abi=softfp -mhard-float sample.c
sample.c:1: sorry, unimplemented: -mfloat-abi=hard and VFP
単に、後ろに書いたオプションに上書きされているだけだと思っていたのですが。。。
-mhard-floatと-msoft-floatのそれぞれに、(floatのABIとして)-mfloat-abi=softfpか否かが存在する
のではなく、
-float-abi=hard or soft or softfp の三者択一
だと理解しているのですが、何か盛大に勘違いしているでしょうか?
ソースを追って見ればはっきりすることですが、gccのアーキテクチャ共通の部分の処理として -mhard-floatのオプションを解釈したときに内部的なfloat-abiのフラグもhardにセットしていて、ARMアーキテクチャ固有の部分の処理で-mhard-floatとfloat-abi=hardの組み合わせをエラーにしているだけかと。そのエラーチェックのところでは明示的に-mfloat-abi=のオプションで指定されたのか暗黙的に設定されたのかは見ていない(またはその時点では情報が残っていない)のでしょう。
PowerPCやMIPSでは-mhard-floatの指定で連動してfloat-abi=hardになるほうが都合がよいし、それで問題がないのですが、ARMだけが特殊でややこしいことになっています。