はじめに
- この記事はひとりNEONアドベントカレンダー2020 4日目の記事です
- 昨日のaddに引き続き、今日は水平加算系の命令を紹介する
水平加算命令
- 水平加算は、ベクトル内の全要素の和を取る命令
- 入力は複数だけど、出力は単一の要素 (なので、厳密には SI M Dではない、と誰かが言っていた)
- 水平加算系というだけあって、いくつかあるので、
padd
、paddl
、addv
、addlv
の4つの命令を紹介する- ちなみに、名前からして一見水平加算しそうな
hadd
命令はhalving add
で、和を取ったあと右に1bitシフトする命令。つまり水平加算ではない。
- ちなみに、名前からして一見水平加算しそうな
- 本当に
add
命令多すぎである。
padd
命令
この図は4年前に使ったやつの再掲だけど、基本的に結果の
n
番目の要素には、入力レジスタの(n*2
番目の要素+n*2+1
番目の要素)が格納される。intrinsic関数のシグネチャとしては引数は、2つのベクトルを取り、同じ型を返す。(ex:
uint8x16_t vpaddq_u8(uint8x16_t a, uint8x16_t b)
)-
先の図は
vpadd_u8
(64bit幅の命令)を表した図だが、vpaddq_u8
(128bit幅の命令)でも、1つ目のレジスタと2つ目のレジスタをあわせて256bit幅の仮想的なレジスタに対して処理が行われる。入力レジスタのn*2
番目の要素とn*2+1
番目の要素の和が、出力レジスタのn
番目の要素として格納される-
vpadd
命令であれば入力は64bit幅のレジスタ2つで128bit幅。総和を計算するには4回の命令(演算)が必要になる。 -
vpaddq
命令であれば同5回の命令(演算)が必要になる
-
SSEでは、一発で水平加算を求める命令があったが、NEONにはこの
padd
命令しかなかった。 いままでは。
addv
命令
- NEONは、Arm v8の命令セットに含まれた、と何度も書いたけれど、実はその際、
double
演算を始めとする、Arm v7時代には存在しなかった命令たちが追加されている - 前述の図は
vaddvq_u8
で128bit幅のレジスタの、8bit整数型の各要素の和を1回で計算している1 -
addv
命令もその一つで、こちらは、SSEの水平加算命令よろしく、一発で各要素ごとの和を求めてくれる - 戻り値は、各要素の型が使われる(
vaddv_u8
ならばuint8_t
型、vaddv_s32
ならint32_t
型)
paddl
命令
-
paddl
命令では加算する際に各要素をbit拡張する。これにより演算結果がオーバーフローすることを防げる。 -
paddl
命令は単一のレジスタしか受け付けない。 - 隣り合う要素ごと加算し、結果を倍の要素に格納して返す。(ex:
uint8x16_t
を受けてuint16x8_t
型を返す)
addlv
命令
- この流れで行くとなんとなく分かると思うけれど、
addv
命令の結果を、オーバーフローを防ぐために要素の型より一段広い型で返す- ex:
vaddlv_u8
命令だと、各要素はuint8_t
だが、処理結果はuint16_t
型で返ってくる - こちらも、入力として64bit幅、128bit幅、どちらも受け取る
- ex:
その他
-
vpadd_f16
ならびにvpaddq_f16
命令が存在するが、こいつらはArm v8.2拡張命令セットなので、実行時にCPUがサポートしているかチェックが必要
おわりに
- 本当に
add
命令多すぎである - 明日も私の担当の予定です
-
今気づいたんだけれど、4年前の記事は無駄に
padd
命令を何回も計算してたから、addv
命令でハミング距離を再計算したら結果が覆る可能性が微レ存? ↩