2013年06月27日
ARMのNEONのSIMD命令をgccのオートベクタライズの最適化で使う方法
以前にgccでNEONのSIMD命令を生成させる方法という記事を書きましたが、今はかなり状況が変わっています。コンパイラのオートベクタライズの最適化はずっと進化していて、簡単なコンパイルオプションをつけるだけでNEONのSIMD命令を活用することができるようになっています。
Ubuntu 12.04LTSのarm-linux-gnueabihf-gccを使用しています。
$ arm-linux-gnueabihf-gcc -v Using built-in specs. COLLECT_GCC=arm-linux-gnueabihf-gcc COLLECT_LTO_WRAPPER=/usr/lib/gcc/arm-linux-gnueabihf/4.6/lto-wrapper Target: arm-linux-gnueabihf Configured with: ../src/configure -v --with-pkgversion='Ubuntu/Linaro 4.6.3-1ubuntu5' --with-bugurl=file:///usr/share/doc/gcc-4.6/README.Bugs --enable-languages=c,c++,fortran,objc,obj-c++ --prefix=/usr --program-suffix=-4.6 --enable-shared --enable-linker-build-id --with-system-zlib --libexecdir=/usr/lib --without-included-gettext --enable-threads=posix --with-gxx-include-dir=/usr/arm-linux-gnueabihf/include/c++/4.6.3 --libdir=/usr/lib --enable-nls --enable-clocale=gnu --enable-libstdcxx-debug --enable-libstdcxx-time=yes --enable-gnu-unique-object --enable-plugin --enable-objc-gc --enable-multilib --disable-sjlj-exceptions --with-arch=armv7-a --with-float=hard --with-fpu=vfpv3-d16 --with-mode=thumb --disable-werror --enable-checking=release --build=x86_64-linux-gnu --host=x86_64-linux-gnu --target=arm-linux-gnueabihf --program-prefix=arm-linux-gnueabihf- --includedir=/usr/arm-linux-gnueabihf/include --with-headers=/usr/arm-linux-gnueabihf/include --with-libs=/usr/arm-linux-gnueabihf/lib Thread model: posix gcc version 4.6.3 (Ubuntu/Linaro 4.6.3-1ubuntu5)
これより新しいgcc 4.7, 4.8を使っても同様です。
整数演算のベクタライズ
以下の関数はint配列の要素の相加平均を求めるものです。こんな単純な関数でもベクタライズの最適化を適用できます。
int int_average(int* array, int size) { int i; long long total = 0; if (size <= 0) { return 0; } for (i = 0; i < size; i++) { total += array[i]; } return total / size; }
最適化オプション -O2の場合
$ arm-linux-gnueabihf-gcc -mfpu=neon -O2 -S int_average.c
int_average: @ args = 0, pretend = 0, frame = 0 @ frame_needed = 0, uses_anonymous_args = 0 push {r3, r4, r5, lr} subs r3, r1, #0 ble .L4 subs r2, r0, #4 movs r1, #0 movs r0, #0 movs r4, #0 .L3: ldr r5, [r2, #4]! adds r4, r4, #1 adds r0, r0, r5 adc r1, r1, r5, asr #31 cmp r4, r3 bne .L3 mov r2, r4 asrs r3, r4, #31 bl __aeabi_ldivmod pop {r3, r4, r5, pc} .L4: movs r0, #0 pop {r3, r4, r5, pc}
最適化のオプション -O3 をつけるとベクタライズを行います。
$ arm-linux-gnueabihf-gcc -mfpu=neon -O3 -S int_average.c
... vmov.i32 q9, #0 @ v2di add r2, r0, r2, lsl #2 movs r3, #0 .L7: vldmia r2!, {d16-d17} vmovl.s32 q10, d16 adds r3, r3, #1 vmovl.s32 q8, d17 vadd.i64 q9, q10, q9 cmp r3, ip vadd.i64 q9, q8, q9 bcc .L7 ...
SIMD命令が使われています。
得られた結果は以下に貼りました。
https://gist.github.com/tetsu-koba/5873910
浮動小数点演算のベクタライズ
float float_average(float* array, int size) { int i; float total = 0; if (size <= 0) { return 0; } for (i = 0; i < size; i++) { total += array[i]; } return total / size; }
先ほどと同様の相加平均を求める関数のfloat版です。
arm-linux-gnueabihf-gcc -mfpu=neon -O3 -S float_average.c
float_average: @ args = 0, pretend = 0, frame = 0 @ frame_needed = 0, uses_anonymous_args = 0 @ link register save eliminated. cmp r1, #0 ble .L4 subs r0, r0, #4 movs r3, #0 flds s15, .L7 .L3: adds r3, r3, #1 adds r0, r0, #4 cmp r3, r1 flds s14, [r0, #0] fadds s15, s15, s14 bne .L3 fmsr s13, r3 @ int fsitos s14, s13 fdivs s0, s15, s14 bx lr .L4: flds s0, .L7 bx lr
x86_64では-O3で浮動小数点演算もベクタライズされますが、ARMではそうなりません。その理由はNEONの浮動小数点演算はIEEE754標準の全てを満たしていないためです。 -funsafe-math-optimizations のコンパイルオプションを追加するとベクタライズされるようになります。
ここではもっと簡単に -Ofast をつけることにします。これは「厳密さよりも速度優先」です。 -O3 の最適化に追加して厳密さを欠く最適化も行います。グラフィックスの座標計算のような用途ではこちらが適しています。
arm-linux-gnueabihf-gcc -mfpu=neon -Ofast -S float_average.c
... .L7: adds r2, r2, #1 vldmia r4!, {d18-d19} cmp r2, r5 vadd.f32 q8, q8, q9 bcc .L7 ...
SIMD命令が使われています。
得られた結果は以下に貼りました。
https://gist.github.com/tetsu-koba/5873935
まとめ
ARM Neonでベクタライズの最適化をして欲しいときには -Ofast をつける。整数演算だけでよい場合は -O3でも可。
コメント一覧
ありがとうございました。