NEONとは
ArmのSIMD(Single Instruction Multiple Data)拡張機能。
Arm社のページ1によると、以下の演算をサポートしている。
- 16x8ビット、8x16ビット、4x32ビット、2x64ビットの整数演算
- 8x16ビット、4x32ビット、2x64ビットの浮動小数点演算
この表記は、データ型xレーン数を表している。例えば整数型uint16x8_tはuint16のデータ8個を、128ビットレジスタに収めた型を意味する。
また、複数のレジスタを束ねた型も定義されている。データ型xデータレーン数xレジスタ本数_t(例: uint16x8x3_t)
簡単な例
uint16x8_tのデータ2つを加算する関数
add_uint16.c
#include "arm_neon.h"
void add_uint16_neon(unsigned short int *src1, unsigned short int *src2, unsigned short int *dst) {
uint16x8_t q1 = vld1q_u16(src1);
uint16x8_t q2 = vld1q_u16(src2);
uint16x8_t q0;
q0 = vaddq_u16(q1, q2);
vst1q_u16(dst, q0);
}
アセンブリを確認すると、このvaddq_u16は、add.8h v31, v31, v30というベクトル命令にコンパイルされていることがわかる。
$ gcc -g -O add_uint16.c -c -o add_uint16.o
$ objdump -d add_uint16.o
add_uint16.o: file format mach-o arm64
Disassembly of section __TEXT,__text:
0000000000000000 <ltmp0>:
0: 3dc0001f ldr q31, [x0]
4: 3dc0003e ldr q30, [x1]
8: 4e7e87ff add.8h v31, v31, v30
c: 3d80005f str q31, [x2]
10: d65f03c0 ret
この関数を利用して、以下のようなuint16(unsigned short int)のデータsrc1, src2を入れると、8個分のデータを加算してdstに入れることができる。
add_uint16.c
#include "stdio.h"
void main(void) {
unsigned short int src1[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15};
unsigned short int src2[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15};
unsigned short int dst[16];
add_uint16_neon(src1, src2, dst);
printf("\tsrc1, \tsrc2, \tdst\n");
for (size_t i=0; i<16; i++) {
printf("\t%u, \t%u, \t%u\n", src1[i], src2[i], dst[i]);
}
}
実行結果では前半8個のデータが加算されている。
$ gcc -g -O add_uint16.c -o exe_add_uint16
$ ./exe_add_uint16
src1, src2, dst
0, 0, 0
1, 1, 2
2, 2, 4
3, 3, 6
4, 4, 8
5, 5, 10
6, 6, 12
7, 7, 14
8, 8, 0
9, 9, 0
10, 10, 0
11, 11, 0
12, 12, 0
13, 13, 0
14, 14, 0
15, 15, 0
src1, src2は16要素あるので、演算するデータ数を16個にしてみる。
add_uint16.c
void add_uint16_neon(unsigned short int *src1, unsigned short int *src2, unsigned short int *dst) {
uint16x8x2_t q0, q1, q2;
q1 = vld2q_u16(src1);
q2 = vld2q_u16(src2);
q0.val[0] = vaddq_u16(q1.val[0], q2.val[0]);
q0.val[1] = vaddq_u16(q1.val[1], q2.val[1]);
vst2q_u16(dst, q0);
}
$ ./exe_add_neon_len
src1, src2, dst
0, 0, 0
1, 1, 2
2, 2, 4
3, 3, 6
4, 4, 8
5, 5, 10
6, 6, 12
7, 7, 14
8, 8, 16
9, 9, 18
10, 10, 20
11, 11, 22
12, 12, 24
13, 13, 26
14, 14, 28
15, 15, 30
アセンブリを見るとadd.8hを2回実行していた。
100003e48: 4c40841a ld2.8h { v26, v27 }, [x0]
100003e4c: 4c40843c ld2.8h { v28, v29 }, [x1]
100003e50: 4e7c875e add.8h v30, v26, v28
100003e54: 4e7d877f add.8h v31, v27, v29
100003e58: 4c00845e st2.8h { v30, v31 }, [x2]
100003e5c: d65f03c0 ret