ぐだぐだ低レベルプログラミング(128)ARM64(AArach64)FMLA、ベクトル積和

Joseph Halfmoon

前回から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では例によって半精度がありません。残りのうち単精度のみ練習してお茶を濁してます。手抜き。

    1. 半精度浮動小数
    2. 単精度浮動小数
    3. 倍精度浮動小数
実験につかったアセンブリ言語記述の被テスト関数

例によって手抜きの、関数プロローグ、エピローグ無の被テスト関数です。その動作を図にしたものが以下にFMLAvDiag

ほとんど前回ソースのパクリでないの。。。

.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を横並びにして見やすくしたものが以下に。

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

ぐだぐだ低レベルプログラミング(127)ARM64(AArach64)FSUB、ベクトル減算 へ戻る

ぐだぐだ低レベルプログラミング(129)ARM64(AArach64)FSQRT(ベクトル)へ進む