5
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

ひとりNEONAdvent Calendar 2020

Day 18

固定小数点演算用命令

Last updated at Posted at 2020-12-17

はじめに

そもそも固定小数点演算とは

  • OpenCV アドベントカレンダー2020にも寄稿したが、浮動小数点演算ではなく、整数型の演算で小数点以下の数値を表現する方法である
  • キモは割り算を表す右bitシフトだが、NEONではそのあたりかゆいところに手が届く仕様になっている。
  • 代表例として、addhnraddhnrshr_nhadd命令を紹介する

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レジスタとして返される。
  • vaddhnvaddhn_highをそれぞれ呼べば、レーン数倍になった整数型のベクトルが得られる。
  • 念の為追記しておくと、「固定小数点型」という型は存在しない。NEONでは、各要素幅2nbitのうち、下位nbit分を小数点以下を表す領域として扱う、という話。
    • なので、
    • 引数の要素が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
  • 演算結果をaddhnraddhnで見比べてみると、演算結果が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と計算されていることがわかる。

vhaddvrhadd

  • どんどん続くよ固定小数点演算用命令
  • vhadd命令は加算した後右1bitシフト、
  • vrhadd命令は加算した後1さらに足してから右1bitシフト
  • 最小単位が1/2である固定小数点演算が可能
  • こいつらは入力と出力の型が全く同じなので、128bit幅のvhaddq命令、vrhaddq命令も提供される。

終わりに

  • NEONでは、浮動小数点演算を充実させる代わりに固定小数点演算が豊富にサポートされている印象を受ける
  • 多分、ArmのCPUはもともと組み込み向けがターゲットだった経緯があり、固定小数点演算が多数サポートされているのだと思う。
  • 明日も手島の執筆担当で、mov命令を紹介予定
5
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?