はじめに
- この記事はひとりNEONアドベントカレンダー2020 10日目の記事です
-
昨日は
load
命令を紹介した。 - 今日は
load
と対をなすstore
命令を紹介する
$ grep ^vst[1234] /usr/lib/gcc/aarch64-linux-gnu/7.5.0/include/arm_neon.h | cut -f 1 -d ' ' | sed -e 's/_[spfu][0-9]\+//g' | sort | uniq -c
14 vst1
14 vst1_lane
14 vst1q
14 vst1q_lane
14 vst2
1 vst2_lane_
14 vst2q
1 vst2q_lane_
14 vst3
1 vst3_lane_
14 vst3q
1 vst3q_lane_
14 vst4
1 vst4_lane_
14 vst4q
1 vst4q_lane_
vst1
(vstn
)
-
load
命令と対をなす。 -
vst1
命令だとただメモリに保存する通常のstore
命令 -
store
命令はload
と同じくvst2
、vst3
、vst4
まで存在する -
load
ではgatherを実現していたが、store
命令ではscatterを実現する
st3q.cpp
uint8_t data[] = {0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,};
uint8_t dst[48] = { 255 };
uint8x16x3_t vsrc;
vsrc.val[0] = vld1q_u8(data );
vsrc.val[1] = vld1q_u8(data + 16);
vsrc.val[2] = vld1q_u8(data + 32);
vst3q_u8(dst , vsrc);
- 演算結果
0 10 100 1 11 101 2 12 102 3 13 103 4 14 104 5
15 105 6 16 106 7 17 107 8 18 108 9 19 109 10 20
110 11 21 111 12 22 112 13 23 113 14 24 114 15 25 115
- 見事に、
vld3q
命令の逆が行われ、0
と10
と100
から始まる数列がそれぞれ順番に並べられている様子が分かる -
vld3q
命令でロードしたベクトルに対して演算を行い、最後にvst3q
命令で書き込めば、RGB画像において、各色独立の演算を施した上で同じフォーマットでメモリ上に書き戻せる -
vld3q
も神とたたえたが、このvst3q
命令も神の如き尊さである。
vst1_lane
、vst1q_lane
(vstn_lane
)
- この
lane
がついた命令は、実際どう振る舞うのか - 動かして試してみよう
vst2q.cpp
float data[] = { 1.0, 2.0, 3.0, 4.0, 10.0, 20.0, 30.0, 40.0};
float res [] = { -1.0f, -1.0f, -1.0f, -1.0f, -1.0f, -1.0f, -1.0f, -1.0f};
float32x4x2_t a;
a.val[0] = vld1q_f32(data );
a.val[1] = vld1q_f32(data + 4);
vst2q_lane_f32(res, a, 2);
- 使い方は
vld2q_lane
命令同じで、vst2q_lane
命令にはfloat32x4x2_t
型のように、ベクトルを束ねた型を使う- 第3引数は0オリジンでレーン番号を指定する。
- 当然範囲外を叩くとコンパイルエラーだし、数値はコンパイル時定数の必要がある。
- 演算結果
3 30 -1 -1 -1 -1 -1 -1
- 意外というかなんというか。
-
lane
番号で指定した部分だけが書き出される。 - なので、確保した配列
res[8]
のうち、後ろの6要素は触れられないままである
アライメントについて
- SSEと違い、アライン(先頭アドレスが8 byteの倍数になっているか)を心配しなくてもHW側でよしなにやってくれる。1
- ただし、Githubのissueにコメントしたが、32bit OS上で
unaligned
なアドレスから、u64
、s64
のロードをすると、実行時エラーSIGBUS
が起きる。 - 解せないのは、同アドレスから
vld1q_f32
など、別の命令で適当にロードしても実行時エラーは起きず、ロードにvreinterpretq_xx
で型を変えることが可能である - つまり「同じアドレス」から全く問題なくロードができる。
- 解せないけれど、HWの仕様っぽいので割り切るしか無い。
チップの命令セット | OSの32bit/64bit | ベクトルレジスタの型 | 8byte alignment | 結果 |
---|---|---|---|---|
Armv8 | 64bit | int32x4_t |
Y | Success |
Armv8 | 64bit | int32x4_t |
N | Success |
Armv8 | 64bit | int64x2_t |
Y | Success |
Armv8 | 64bit | int64x2_t |
N | Success |
Armv8 | 32bit | int32x4_t |
Y | Success |
Armv8 | 32bit | int32x4_t |
N | Success |
Armv8 | 32bit | int64x2_t |
Y | Success |
Armv8 | 32bit | int64x2_t |
N | Error (SIGBUS ) |
Armv7 | 32bit | int32x4_t |
Y | Success |
Armv7 | 32bit | int32x4_t |
N | Success |
Armv7 | 32bit | int64x2_t |
Y | Success |
Armv7 | 32bit | int64x2_t |
N | Error (SIGBUS ) |
- アセンブラレベルで見てみると、Arm v8 では当然違いは無い
- Arm v7 のアセンブラを見てみると、
-
int64x2_t
の場合(含むuint64x2_t
、int64x1_t
、uint64x1_t
)
vld1.64 {d16-d17}, [r3:64]
- それ以外の型の場合
vld1.32 {d16-d17}, [r3]
-
{d16-d17}
は2本の64bitレジスタを指定している。 - 命令のオペランドの
[r3]
は一般レジスタのr3
に入ってるアドレスからロードする、という意味 - 違いは
-
vld1.64
かvld1.32
という末尾についてる要素の型 -
[r3]
というアドレス指定が[r3:64]
となっている - 多分、この
:64
が末尾についてることで8byte境界を期待しているのだと推測する
-
- 何にしろそれ以外のロードに関してはアライメントの心配は要らない。
おわりに
-
store
命令のvst
とその派生形を紹介しました - 明日も手島の執筆の予定で、積和命令を紹介予定
-
SSEでは16byte境界だが、NEONでは、前述の通りごく一部の命令に限って8byte境界の必要性がある ↩