ぐだぐだ低レベルプログラミング(18) Arm NEONをつかってみる3

JosephHalfmoon

続けざまにぐだぐだです。前回、なんとかコンパイラがクワッドワード(128ビット)幅のレジスタを使ったNeon命令を吐き出すようになりましたが、何か思っていたより複雑なコードになっていました。もしやと思ってよく考えてみたら、それは私の書いたコードがイケないことに気付きました。それでもコンパイラ様は必死に注文にこたえるべく、コード生成をおこなってくれた結果、なにやら複雑なコードが生成されてしまったのでした。自分の書いたコードをコンパイラ様が素直に解釈できるようにちょいと直せば良かったのでした。それでようやく、クワッドワードかつシンプルなコードが出力されるようになりました。

まずは、前々回のうまく行かなかったコードを再掲載させていただきます。

void vector_mul(const int vlen, float* __restrict x, float* __restrict y, float* __restrict result) {
    for(int i = 0; i < vlen; i++) {
        result[i] = x[i] * y [i];
    }
}

上の関数を含むソース全体を以下のようにしてコンパイルすると

$ gcc -o ns1 -O3 -mfpu=neon -ftree-vectorize ns1.c

こんな命令に展開されたのでした。

0001051c <vector_mul>:
1051c: e3500000 cmp r0, #0
10520: d12fff1e bxle lr
10524: e0810100 add r0, r1, r0, lsl #2
10528: ecf17a01 vldmia r1!, {s15}
1052c: ecb27a01 vldmia r2!, {s14}
10530: e1500001 cmp r0, r1
10534: ee677a87 vmul.f32 s15, s15, s14
10538: ece37a01 vstmia r3!, {s15}
1053c: 1afffff9 bne 10528 <vector_mul+0xc>
10540: e12fff1e bx lr

vで始まる命令こそ使われているものの、演算対象のレジスタはと言えば、s15とかs16とか、1レジスタに1個の単精度浮動小数の型式。本当は、1レジスタに4個単精度浮動小数が格納できる qで始まるレジスタを使いたかったのです。それで前回、無理やりコンパイルオプションをごてごてつけて q で始まるレジスタが使われるようにいたしました。

$ gcc -o ns1 -O3 -mfpu=neon -ftree-vectorize -mvectorize-with-neon-quad -ffast-math ns1.c

しかしね、単なるベクトルの要素毎の掛け算にしては、前回のコンパイル結果、複雑。そこでハタと気付きました。vlenという引数で実際に計算する要素数をカウントしているのですが、自分じゃ「分かっている」ので4の倍数の数字しか(コマンドライン引数経由で)vlenには渡してませんでした。けれど、コンパイルする時、コンパイラ様にしたらvlenの値なんか分からない。奇数かもしれない、偶数でも4の倍数じゃないかもしれない。コンパイラ様としては、そのようなケースの全てでちゃんと動くコードを生成しないとならない。。。すみません。qで始まるレジスタ使いたかっただけなのです。よって、以下のようにチョイ変いたしました。

void vector_mul(const int vlen, float* __restrict x, float* __restrict y, float* __restrict result) {
        for(int i = 0; i < 4*vlen; i++) {
        result[i] = x[i] * y [i];
    }
}

違い分かります? 4倍してます。vlenに何を渡そうとも、要素数は4の倍数に決まっていますわな。修正版のソースをばコンパイルしていただきます。

$ gcc -o ns2 -O3 -mfpu=neon -ftree-vectorize -mvectorize-with-neon-quad -ffast-math ns2.c

結果、生成されたコードがこちら

00010528 <vector_mul>:
10528: e1a00100 lsl r0, r0, #2
1052c: e3500000 cmp r0, #0
10530: d12fff1e bxle lr
10534: e0830100 add r0, r3, r0, lsl #2
10538: f4620a8d vld1.32 {d16-d17}, [r2]!
1053c: f4612a8d vld1.32 {d18-d19}, [r1]!
10540: f3400df2 vmul.f32 q8, q8, q9
10544: f4430a8d vst1.32 {d16-d17}, [r3]!
10548: e1500003 cmp r0, r3
1054c: 1afffff9 bne 10538 <vector_mul+0x10>
10550: e12fff1e bx lr

{d16-d17}とq8、{d18-d19}とq9は同じレジスタをダブルワード2個とみるか、クワッドワード1個とみるかだけの違いなので、実体としては、q8, q9のクワッドワードのレジスタ2個でロード、ロード、掛け算、ストアと計算していることがわかります。ようやく、最初に頭の中に思い描いたNEONのコードを出していただきました。悪かったのは私ですが、ここまでに3回も使ってしまった。次回こそは、も少しNEONの勉強を進めたい。

ぐだぐだ低レベルプログラミング(17) Arm NEONをつかってみる2

ぐだぐだ低レベルプログラミング(18) Arm NEONをつかってみる4