はじめに
- この記事はひとり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境界の必要性がある ↩
