
前回からA64のベクトル(SIMD)演算命令に入ってます。今回は、はやくも「核心」的なSIMDの積和算を練習してみます。なんでSIMD使うのかと問われれば半分くらいは積和したいから、ということになるんじゃないかと思うからです。SIMD積和算にも浮動小数、整数の両方あるのですが今回は単精度浮動小数のみ。手抜き。
※「ぐだぐだ低レベルプログラミング」投稿順indexはこちら
※実機動作確認には以下を使用しております。
- 
- Raspberry Pi 4 model B、Cortex-A72コア(ARMv8-A)
- Raspberry Pi OS (64bit) bullseye
- gcc (Debian 10.2.1-6) 10.2.1 20210110
 
ARMv8もいろいろレベルがあり、Arm Cortex-A72はARMv8の中でもベーシックな(命令数の少ない)ARMv8p0です。
※A64の最新のマニュアルは以下でダウンロード可能です。
Arm Architecture Reference Manual for A-profile architecture
FMLA(vector)、浮動小数の積和算
第1オペランドをアキュムレータ、第2,第3オペランドを乗算ソースとして第2と第3を掛けた結果を第1と加算し、第1に書き戻すという操作です。1命令の中に2演算含まれる形で、演算は fused です。fusedについてはこちら。
マニュアル的には以下の3種のオペランド幅を取り得るのですが、Armv8p0では例によって半精度がありません。残りのうち単精度のみ練習してお茶を濁してます。手抜き。
- 
- 半精度浮動小数
- 単精度浮動小数
- 倍精度浮動小数
 
実験につかったアセンブリ言語記述の被テスト関数
例によって手抜きの、関数プロローグ、エピローグ無の被テスト関数です。その動作を図にしたものが以下に
ほとんど前回ソースのパクリでないの。。。
.globl	fmlav
.text
.balign	4
fmlav:
    ld1  {v0.4S}, [x0], #16
    ld1  {v1.4S}, [x0], #16
    ld1  {v2.4S}, [x0], #16
    fmla v0.4S, v1.4S, v2.4S
    st1  {v0.4S}, [x0]
    ret
C言語記述のmain関数
上記のアセンブリ言語関数を呼び出すmain関数が以下に。FMLAをやる前のメモリ初期値をダンプ、アキュムレータ初期値とソース2つをベクトルロード後、FMUL呼び出し、アキュムレータに残った結果をメモリにストアして再びダンプというお手軽コードです。
#include <stdio.h>
#include <stdint.h>
#define MAXMEM	(16)
float TargetMEM[MAXMEM];
extern void fmlav(float *);
void initTGT(float c) {
    for (int i=0; i < MAXMEM; i++) {
        TargetMEM[i] = c * (i+1);
    }
}
void dumpTGT(const char *arg) {
    printf("%s\n", arg);
    for (int i=0; i < MAXMEM; i++) {
        printf("%d: %f\n", i, TargetMEM[i]);
    }
}
int main(void) {
    initTGT(1.000f);
    dumpTGT("Before FMLAv");
    fmlav(TargetMEM);
    dumpTGT("After  FMLAv");
    return 0;
}
実験結果
以下のようにしてビルドして実行しています。
$ gcc -g -O0 fmlav.c fmlav.s $ ./a.out
標準出力に現れたBefore、Afterを横並びにして見やすくしたものが以下に。

単精度浮動小数といいつつ、整数部分しか使ってないじゃないの。目で検算するのがメンドかったのよ。