前回は「実はA64のSIMD(ベクトル)命令にMOVなんてない」の回でした。今回はMOVじゃないけどMOVする命令があるの回です。INSとDUP。INSは前回の復習になりますが、DUPは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
DUP(ベクトル・エレメント)命令
ベクトルや配列などを操作しているときに、全ての要素に同じ数を乗じたい(スカラー倍)とか、同じ数を足したい(並行移動)など「同じ数」が期待されるケースがままあります。SIMD(ベクトル)命令は複数の要素を1命令で演算できるだけに、ベクトルの片割れは「同じ要素を詰めたベクトル」となります。そんなときに活躍するのがDUP(ベクトル・エレメント)命令です。
以下は後の処理例での操作を図にしたものです。ベクトルの1要素をDUPlicateして並べているのが分かるかと思います。
上記は単精度浮動小数なので「要素のMOV」4回を1命令で「始末」してますが、これがバイト型であれば16回に相当する操作が1命令でできることになります。MOVの一種なんだけれども時短ってやつ。タイパが良いのよ。
INS(ベクトル・エレメント)命令
上記の「要素のMOV」にあたる1要素を他の要素にチマチマとMOVする操作がINS(ベクトル・エレメント)命令です。実は、前回のベクトル要素のMOVのところで既に実習済です。MOVはINSのエイリアスだったのねえ。折角なので、今回も実習してます。ただし、ニーモニックはINSを使って。
実験につかったアセンブリ言語記述の被テスト関数
当然、DUPにしろ、INSにしろバイトからダブルワード(倍精度浮動小数)にいたる4種類のビット幅の要素に対応しているのですが、例によって手抜きで単精度浮動小数のみの実習であります。また、SIMDのオペランドのレジスタ幅としても64ビット幅もとれるのですが、大は小を兼ねる(ホントか?)ということで128ビット幅の時だけです。
これまた例によって手抜きの関数プロローグ、エピローグ無の被テスト関数のソースが以下に。
.globl insv, dupv .text .balign 4 dupv: ld1 {v1.4S}, [x0], #16 ld1 {v0.4S}, [x0], #16 dup v0.4S, v1.S[1] st1 {v0.4S}, [x0] ret insv: ld1 {v1.4S}, [x0], #16 ld1 {v0.4S}, [x0], #16 ins v0.S[1], v1.S[2] st1 {v0.4S}, [x0] ret
SIMDのオペランド、意味の違いで微妙に書き方が変わるのでちょいちょい間違えてアセンブラに怒られます。
C言語記述のmain関数
上記のアセンブリ言語関数を呼び出すmain関数が以下に。例のとおりで前回のソースの「チョイ変」っす。メモリにテキトーな値を詰め込んでおいて、レジスタにLOAD、LOAD、DUPしてSTORE、続いて同じことをINSで繰り返すっと。芸がないな。
#include <stdio.h> #include <stdint.h> #define MAXMEM (24) float TargetMEM[MAXMEM]; extern void dupv(float *); extern void insv(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 dup vector"); dupv(TargetMEM); dumpTGT("After dup vector"); insv(&TargetMEM[12]); dumpTGT("After ins vector"); return 0; }
実験結果
以下のようにしてビルドして実行しています。
$ gcc -g -O0 insdup.c insdup.s $ ./a.out
標準出力に「ダラダラ」現れる結果を折りたたんで、見やすいように関係個所を枠で囲って紐づけてみましたです。
DUPできてるみたい。でもまだまだMOV一族はいるのよ。