はじめに
- この記事はひとりNEONアドベントカレンダー2020 17日目の記事です
-
昨日は
reinterpret
命令という、イマイチ何が嬉しいのかわからない命令を紹介した - 今日は
popcount
命令を紹介する
popcount
とは
-
特徴量空間の距離、ごちうサーチ 、ハミング距離の計算など、様々な場面で、あるベクトル内の2進数表記での
1
の数を数えたくなることがある - 詳細は割愛するが、
popcount
命令を使うことで、高速に1
の数を数えることができ、幸せになれる - 以下に、
popcount
の具体例をいくつか紹介
入力 | 16進数表記 | 2進数表記 |
popcount の結果 |
---|---|---|---|
1 | 0x0001 | b0000000000000001 | 1 |
15 | 0x000f | b0000000000001111 | 4 |
16 | 0x0010 | b0000000000010000 | 1 |
100 | 0x0064 | b0000000001100100 | 3 |
500 | 0x01f4 | b0000000111110100 | 6 |
1000 | 0x03e8 | b0000001111101000 | 6 |
cnt
命令
-
公式リファレンスを見てみると、
cnt
命令はレジスタ幅の違いを含んでも以下の6種類しか存在しない
vcnt_p8
vcnt_s8
vcnt_u8
vcntq_p8
vcntq_s8
vcntq_u8
- 先程の図でも同じ状況である
- GCCのヘッダ
arm_neon.h
を覗いても状況は同じである
$ grep ^vcnt /usr/lib/gcc/aarch64-linux-gnu/7.5.0/include/arm_neon.h
vcnt_p8 (poly8x8_t __a)
vcnt_s8 (int8x8_t __a)
vcnt_u8 (uint8x8_t __a)
vcntq_p8 (poly8x16_t __a)
vcntq_s8 (int8x16_t __a)
vcntq_u8 (uint8x16_t __a)
-
p8
型は多項式型なので、ここでは割愛する - となると引数は
int8x8_t
、uint8x8_t
、int8x16_t
、uint8x16_t
の4種類しかない - で、前述の処理を考えると、符号ありだろうと符号なしだろうと、bitの数を数えることには変わりないので、結果として、
cnt
命令は1種類しかないことになる - では、例えば
int32x4_t
型のpopcount
処理はどうすれば良いのか?
そこでreinterpret
命令ですよ
- と、ここで昨日紹介した、
reineterpret
命令が効いてくる -
int32x4_t
型のpopcount
のためには以下のようなコードを書く
int32_t src[] = {1, 15, 100, 500};
int32x4_t vsrc = vld1q_s32(src);
int32x4_t vdst = vpaddlq_s16(vpaddlq_s8(vcntq_s8(vreinterpretq_s8_s32(vsrc))));
- 演算結果
0:1
1:4
2:3
3:6
- 前述の表と同じ結果が得られている
-
4年前の記事でも触れているが、下図のように、NEONでは、8bitごとの
popcount
を求める命令しか存在せず、32bitごとの結果を得るためにはレーン内で水平加算が必要となる
- この様にbit単位で独立している命令は、入力の型がなんだろうと中身の処理は同じ、という命令が存在する
- そのため、
reinterpret
命令でただのbitの集合と解釈してやれば、1つの命令を使い回すだけで演算が済む -
8bit
より細かくは分けられないので、popcount
は8bit単位の結果を返す- 32bbitごとに返しちゃうと、それ以上細かい型に適用できないから、最大公約数的な
int8x16_t
を返すcntq
命令1種類だけ実装されている(のだと思う)
- 32bbitごとに返しちゃうと、それ以上細かい型に適用できないから、最大公約数的な
おわりに
- 今日は2進数表記でベクトル内の
1
の数を数えるpopcount
命令を紹介した - 明日も手島の執筆の予定で、固定小数点演算用命令を紹介する予定