はじめに
- この記事はひとりNEONアドベントカレンダー2020 18日目の記事です
-
昨日は
popcount
命令を紹介した - 今日は固定小数点演算用命令を紹介する
そもそも固定小数点演算とは
- OpenCV アドベントカレンダー2020にも寄稿したが、浮動小数点演算ではなく、整数型の演算で小数点以下の数値を表現する方法である
- キモは割り算を表す右bitシフトだが、NEONではそのあたりかゆいところに手が届く仕様になっている。
- 代表例として、
addhn
、raddhn
、rshr_n
、hadd
命令を紹介する
addhn
- 本日初紹介、固定小数点演算用加算命令、
addhn
- 公式によれば"Add returning high narrow"
addhn.cpp
uint16_t data0[] = { 640,624,608,592,576,560,544,512 };
uint16_t data1[] = { 640,624,608,592,576,560,544,512 };
uint16x8_t vsrc0 = vld1q_u16(data0);
uint16x8_t vsrc1 = vld1q_u16(data1);
uint8x8_t v_dst = vaddhn_u16(vsrc0, vsrc1);
0 640 640 5
1 624 624 4
2 608 608 4
3 592 592 4
4 576 576 4
5 560 560 4
6 544 544 4
7 512 512 4
- 1番左から要素番号、第1引数の要素、第2引数の要素、そして1番右に演算結果である
- 一見意味不明だが、これは両引数の和を256で割っている。
- これをすることにより、1/256を最小の単位とした固定小数点数での演算結果を、整数に変換することができる。
- 1/256なのは、結果の各要素が上位8bitのみで、右8bitシフトが256割ることと等価だから。
- また、
high
が末尾に付くバージョンもある。こちらは、引数がさらに1つ増え、3引数を取る
arm_neon.h
int8x16_t
vaddhn_high_s16 (int8x8_t __a, int16x8_t __b, int16x8_t __c)
- 第2引数と第3引数で固定小数点数演算を行い、結果を第1引数と連結して128bitレジスタとして返される。
-
vaddhn
とvaddhn_high
をそれぞれ呼べば、レーン数倍になった整数型のベクトルが得られる。 - 念の為追記しておくと、「固定小数点型」という型は存在しない。NEONでは、各要素幅
2n
bitのうち、下位n
bit分を小数点以下を表す領域として扱う、という話。- なので、
- 引数の要素が
int32_t
の場合はシフト幅は16bit、65536で割ることに相当する - 引数の要素が
int64_t
の場合はシフト幅は32bit、4294967296で割ることに相当する
raddhn
-
addhn
のお友達、固定小数点演算用加算命令、raddhn
-
addhn
と似た演算をするが、右bitシフトして整数に戻す前に、定数を足し合わせる。 - 例えば出力の各要素が8bit幅の場合、
addhn
命令と同じく256で割り算が行われるが、その際256の半分の128が加算される - これにより、
raddhn
の演算結果は「切り捨て」ではなく、「最近接丸め」、いわゆる四捨五入が行われる。
raddhn.cpp
uint16_t data0[] = { 640,624,608,592,576,560,544,512 };
uint16_t data1[] = { 640,624,608,592,576,560,544,512 };
uint16x8_t vsrc0 = vld1q_u16(data0);
uint16x8_t vsrc1 = vld1q_u16(data1);
uint8x8_t v_dst = vraddhn_u16(vsrc0, vsrc1);
- 演算結果
0 640 640 5
1 624 624 5
2 608 608 5
3 592 592 5
4 576 576 5
5 560 560 4
6 544 544 4
7 512 512 4
- 演算結果を
addhn
とraddhn
で見比べてみると、演算結果が5
になってる部分が違う。 -
raddhn
命令では丸めを行っているため、和が640(=128 * 5)
より小さい加算結果も、5
に丸め込まれている。
rshr_n
- 引数の中に、単体の数値を使う場合、命令の名前中に
_n_
が含まれる
uint16x4_t
vrshrn_n_u32(uint32x4_t a, b)
- なお、第2引数はコンパイル時定数の必要があるし、ベクトル型の要素のレンジ内を指す必要がある
- この
vrshrn_n
命令は右シフトを行う命令だが、最近接丸めを行うように、右シフト後に捨てられるbitの内、最上位bitが立ってる場合は繰り上げが行われる
rshrn.cpp
int32_t data[] = { 4096, 28411, 17720, 506,};
int32x4_t vsrc = vld1q_s32(data);
int16x4_t vdst0 = vrshrn_n_s32(vsrc, 8);
int16x4_t vdst1 = vshrn_n_s32(vsrc, 8);
- 演算結果
0 4096 16 16
1 28411 111 110
2 17720 69 69
3 506 2 1
- 右から2列目が
vrshrn
命令の結果、一番右の列がvshrn
命令の結果である - 8bit右シフト、つまり256で割ってるのだが、3番目の要素
506
みたいに、「ほぼ256の倍」の値も、ただの右シフトでは1
になってしまうが、rshrn
では四捨五入によって2
と計算されていることがわかる。
vhadd
、vrhadd
- どんどん続くよ固定小数点演算用命令
-
vhadd
命令は加算した後右1bitシフト、 -
vrhadd
命令は加算した後1さらに足してから右1bitシフト - 最小単位が1/2である固定小数点演算が可能
- こいつらは入力と出力の型が全く同じなので、128bit幅の
vhaddq
命令、vrhaddq
命令も提供される。
終わりに
- NEONでは、浮動小数点演算を充実させる代わりに固定小数点演算が豊富にサポートされている印象を受ける
- 多分、ArmのCPUはもともと組み込み向けがターゲットだった経緯があり、固定小数点演算が多数サポートされているのだと思う。
- 明日も手島の執筆担当で、
mov
命令を紹介予定