読者です 読者をやめる 読者になる 読者になる

おぺんcv

画像処理エンジニアのブログ

ARM NEONの使い方 加算編

四則演算編の予定でしたが、量が多いので分割することにしました
今回は加算編です

加算

vadd[q]_<type>(va, vb)

64bit(qが付く場合は128bit)のベクタvaとvbを足します
戻り値のサイズは入力のサイズと同じです

サンプル

符号付き16bit整数のベクタvaとvbをvadd_s16()で足してみます

#include <stdio.h>
#include <stdint.h>
#include <arm_neon.h>

int main()
{
	int16_t a[4] = { 1, -1, 1, -1 };
	int16_t b[4] = { 1, -1, 32767, -32768 };

	int16x4_t va = vld1_s16(a);
	int16x4_t vb = vld1_s16(b);
	int16x4_t vc = vadd_s16(va, vb);

	int16_t c[4];
	vst1_s16(c, vc);

	for (int i = 0; i < 4; i++)
		printf("c[%d]: %d\n", i, c[i]);

	return 0;
}
実行結果
c[0]: 2
c[1]: -2
c[2]: -32768
c[3]: 32767

3,4番目のレーンはオーバーフローしました

符号拡張+加算(long add)

vaddl_<type>(va, vb)

64bitのベクタvaとvbの各レーンのbit幅を倍に拡張して足します
戻り値のサイズは128bitになります

サンプル

符号付き16bit整数のベクタvaとvbをvaddl_s16()で足してみます

#include <stdio.h>
#include <stdint.h>
#include <arm_neon.h>

int main()
{
	int16_t a[4] = { 1, -1, 1, -1 };
	int16_t b[4] = { 1, -1, 32767, -32768 };

	int16x4_t va = vld1_s16(a);
	int16x4_t vb = vld1_s16(b);
	int32x4_t vc = vaddl_s16(va, vb);

	int32_t c[4];
	vst1q_s32(c, vc);

	for (int i = 0; i < 4; i++)
		printf("c[%d]: %d\n", i, c[i]);

	return 0;
}
実行結果
c[0]: 2
c[1]: -2
c[2]: 32768
c[3]: -32769

各レーンが32bitに拡張され、オーバーフローしなくなりました

飽和加算(saturating add)

vqadd[q]_<type>(va, vb)

64bit(qが付く場合は128bit)のベクタvaとvbを足します
演算結果がオーバーフローする場合は最大値/最小値で飽和させます
戻り値のサイズは入力のサイズと同じです

サンプル

符号付き16bit整数のベクタvaとvbをvqadd_s16()で足してみます

#include <stdio.h>
#include <stdint.h>
#include <arm_neon.h>

int main()
{
	int16_t a[4] = { 1, -1, 1, -1 };
	int16_t b[4] = { 1, -1, 32767, -32768 };

	int16x4_t va = vld1_s16(a);
	int16x4_t vb = vld1_s16(b);
	int16x4_t vc = vqadd_s16(va, vb);

	int16_t c[4];
	vst1_s16(c, vc);

	for (int i = 0; i < 4; i++)
		printf("c[%d]: %d\n", i, c[i]);

	return 0;
}
実行結果
c[0]: 2
c[1]: -2
c[2]: 32767
c[3]: -32768

3,4番目のレーンは最大値/最小値で飽和しました

その他の加算

個人的にあまり使わなそうだと思ったものですが
簡単に触れておきます

wide add: vaddw_<type>(va, vb)

サイズが違うもの同士を足す場合に使うようです
例えばvaddw_s16(va, vb)はvaがint32x4_tでvbがint16x4_tです

halving add: vaddh[q]<type>(va, vb)

各レーンを足して2で割ります

(va[i] + vb[i]) >> 1
rounding halving add: vaddhn_<type>(va, vb)

各レーンを足したものにさらに1を足して2で割ります

(va[i] + vb[i] + 1) >> 1
add high half: vaddhn_<type>(va, vb)

各レーンの上位ビット同士を足すようです
例えば、各レーンが32bitなら上位16bit同士を足します
どこで使うんだろう…

#include <stdio.h>
#include <stdint.h>
#include <arm_neon.h>

int main()
{
	int a[4] = { 0, 1, 2, 3 };
	int b[4] = { 0, 1, 2, 3 };

	for (int i = 0; i < 4; i++)
	{
		a[i] = a[i] << 16;
		b[i] = b[i] << 16;
	}

	int32x4_t va = vld1q_s32(a);
	int32x4_t vb = vld1q_s32(b);
	int16x4_t vc = vaddhn_s32(va, vb); // { 0, 2, 4, 6 }

	return 0;
}
rounding add high half: vraddhn_<type>(va, vb)

これ、よく分かりませんw
(知ってたら教えて下さい)

次回

次回は減算編の予定です
ARM NEONの使い方 減算編