Neon指令简介
普通运算都是单指令单数据的,例如加法同时只能对两个数做加法运算,得到一个结果。 Neon 指令是单指令多数据的指令(Single Instruction Multiple Data,简称SIMD),可以对一组数据同时执行一个指令,从而大幅提高并行运算的效率。
Neon 指令本身没什么难度,主要是得熟悉 API,我们先看例子,再看API。
一、使用 Neon 指令将图片转为灰度图
代码从 Java 层传入一张 1600 x 1245 的图片,独立执行 20 次,耗时 5284 ms,平均每次 264ms 。
作为对比,使用相同算法,用普通C++实现,耗时 12826ms,平均每次 641ms 。
使用 Neon 的代码:
jintArray deColorUseNeon(JNIEnv *env, jsize size, jint* colors) {
// 保存结果(去色后的图片)
auto *p = new unsigned char [size * 4];
// 像素的头指针
const auto* colorsHead = (const unsigned char* )colors;
// 8(每个颜色8-bit) x 8(一次处理8个像素) x 4(ARGB)
uint8x8x4_t currentPixels;
unsigned char c38[] = {38, 38, 38, 38, 38, 38, 38, 38};
uint8x8_t _38 = vld1_u8(c38);
unsigned char c75[] = {75, 75, 75, 75, 75, 75, 75, 75};
uint8x8_t _75 = vld1_u8(c75);
unsigned char c15[] = {15, 15, 15, 15, 15, 15, 15, 15};
uint8x8_t _15 = vld1_u8(c15);
for (unsigned int i = 0; i < size * 4; i += 32) {
// 读取 8 个像素
currentPixels = vld4_u8(colorsHead + i);
// 利用公式 (r * 38 + g * 75 + b * 15) / 128 计算灰度值:
// 乘以每个颜色各自的系数
uint16x8_t tempR = vmull_u8(currentPixels.val[2], _38);
uint16x8_t tempG = vmull_u8(currentPixels.val[1], _75);
uint16x8_t tempB = vmull_u8(currentPixels.val[0], _15);
// 相加
uint16x8_t sum = vaddq_u16(tempR, tempG);
sum = vaddq_u16(sum, tempB);
// 除以 128,得到灰色通道的值
uint8x8_t gray = vshrn_n_u16(sum, 7);
currentPixels.val[2] = gray;
currentPixels.val[1] = gray;
currentPixels.val[0] = gray;
// 保存到结果中
vst4_u8(p + i, currentPixels);
}
jintArray array = env->NewIntArray(size);
env->SetIntArrayRegion(array, 0, size, (const jint *)p);
return array;
}
传统方式实现:
jintArray deColor(JNIEnv *env, jsize size, jint* colors) {
// 保存结果(去色后的图片)
jint *p = new jint[size];
unsigned char r, g, b;
unsigned char temp;
int* num;
int alpha;
for (unsigned int i = 0; i < size; i++) {
num = colors + i;
alpha = ((*num) >> 24) & 0xFF;
r = ((*num) >> 16) & 0xFF;
g = ((*num) >> 8) & 0xFF;
b = (*num) & 0xFF;
// 利用公式 (r * 38 + g * 75 + b * 15) / 128 计算灰度值:
temp = (r * 38 + g * 75 + b * 15) >> 7;
p[i] = (alpha << 24) | (temp << 16) | (temp << 8) | temp;
}
jintArray array = env->NewIntArray(size);
env->SetIntArrayRegion(array, 0, size, p);
return array;
}
两者的去色效果是相同的。
二、指令集介绍
使用 Neon 指令需要在 C++ 中引入头文件
。
这个头文件中定义了支持的数据类型和数据结构,以及各个指令函数。
(1) 数据结构
数据结构有两类: (1) 基本的一维向量;(2) 多维向量。
基本的一维向量:
数据类型的命名规则: [type][size] x [count] _t
例如 int8x8_t
表示 int 类型的向量,单个元素占16 bit,有4个元素,总共占据64 bit。
例如 uint32x4_t
表示 unsigned int 类型的向量,单个元素的大小是32 bit,有4个元素,总共占128 bit。
常见的类型有:
int8x8_t, int16x4_t, int32x2_t, int64x1_t,
float32x2_t, float16x4_t, uint8x8_t, uint16x4_t,
uint32x2_t, uint64x1_t
int8x16_t, int16x8_t, int32x4_t, int64x2_t,
float32x4_t, float16x8_t, uint8x16_t, uint16x8_t,
uint32x4_t, uint64x2_t
多维向量:
多维向量是基本数据类型的数组形式,命名规则: [type][size] x [count] x [lengthOfArray] _t。
例如 uint8x16x4_t
表示 uint8x16_t
的数组,长度为4,常用与ARGB图像处理,它的定义如下:
typedef struct uint8x16x4_t {
uint8x16_t val[4];
} uint8x16x4_t;
(2) 指令
指令按照作用划分,分为:
(1) 加载(把数据从内存装载到寄存器);
(2) 保存(把数据从寄存器保存到内存);
(3) 加减乘运算(没有除);
(4) 与(&)、或(|)、异或(^) 运算;
(5) 其他。
按照操作的数据大小,分为:
正常指令、窄指令、长指令、饱和指令等。
我们按照作用去了解比较好,因为相同作用的指令命名有规律。
(具体指令比较多,后续每种指令单独附上链接)
- 数据加载指令 (Load)
- 数据保存指令 (Store)
- 加法指令 (点我查看详细)
- 减法指令 (点我查看详细)
- 乘法指令 (点我查看详细)
- 比较指令 (点我查看详细)
- 位运算指令
- 其他指令。