参考原文:
android平台的neon优化策略
Neon Intrinsics各函数介绍
目前市面上主流的旗舰android手机搭载的Soc都是64位的CPU,常见的armv7指令集的公版架构如Cortex-A8,Cortex-A9,Cortex-A15,常见的armv8指令集的公版架构如Cortex-A53,Cortex-A57,Cortex-A72,Cortex-A73。arm架构的CPU从armv7a开始已经支持neon(可选项),从而实现并行计算功能。armv8a还具备32个128neon寄存器,并且支持双精度浮点数。
下面为我添加的代码注释 (第一篇参考文章中错把 Android 的位图 RGBA 格式当做 ARGB 进行处理了):
如图,很明显 alpha 通道是最后一个,但是值得注意的是,当使用 u16 时,数据格式为 (20541=0x503D)(65463=0xFFB7) 对应 rgba 中的 GR 和 AB
void method_rgba2gray_neon(AndroidBitmapInfo info, void *pixels) { // Gray = (R*38 + G*75 + B*15) >> 7 TickMeter tm3; tm3.start(); unsigned short *dst = (unsigned short *) pixels; // 注意是 unsigned short unsigned char *src = (unsigned char *) pixels; uint8x8_t r = vdup_n_u8(38); // 将一个标量扩展城向量 8 bit * 8 uint8x8_t g = vdup_n_u8(75); uint8x8_t b = vdup_n_u8(15); uint16x8_t alp = vdupq_n_u16(255 << 8); uint16x8_t temp; uint8x8_t gray; uint8x8x4_t rgba; uint16x8_t high; uint16x8_t low; uint16x8x2_t res; int i, size = info.height * info.width / 8; // 暂不考虑不能被 8 整除的情况 for (i = 0; i < size; ++i) { // 获取r、g、b值,计算灰度值 rgba = vld4_u8(src); // 从内存中加载数据(以 AOS 存储)到 4 个向量(8 bit * 8)中(SOA) temp = vmull_u8(rgba.val[0], r); // vmul 指令 temp = vmlal_u8(temp, rgba.val[1], g); // vmla 指令 temp = vmlal_u8(temp, rgba.val[2], b); gray = vshrn_n_u16(temp, 7); // vshrn_n_u16 会在做右移 7 位的同时将2字节无符号型转成1字节无符号型 src += 8 * 4; // src 移到下个位置 // 赋值 4 通道 rgba(将 gray 扩展成 rgba) // 先用 vmovl_u8 (长指令)符号扩展或零扩展双字向量中的每个元素到其原始长度的两倍 // vqmovn_* 为窄指令 high = vorrq_u16(alp, vmovl_u8(gray)); // 再用 vorrq_u16 按位或运算 low = vorrq_u16(vshlq_n_u16(vmovl_u8(gray), 8), vmovl_u8(gray)); // 注意这里是左移 res = vzipq_u16(low, high); // 两个向量的元素交错打包成一个新向量 vst1q_u16(dst, res.val[0]); // 存储一个向量(res 的前半部分)到内存(16bit*8) dst += 8; vst1q_u16(dst, res.val[1]); dst += 8; } tm3.stop(); LOGI("method_rgba2gray_neon time: %lf", tm3.getTimeMilli()); LOGI(" \n"); }
vmovl_u8,vshlq_n_u16 函数的执行结果分别如下:
其中结果 16进制表示的形式为
86:0x0056
22016:0x5600
vzipq_u16 函数执行(low:GR, high:AB的格式)的结果如下:
可以看出 res[0] 和 res[1] 两个向量分别由 low, high 两个向量一前一后交错打包而成。
其中,
22102:0x5656
65366:0xFF56
程序运行效果如下:
我在 debug/release 模式下分别进行了测试,发现作者原来的方法竟然还不如 c 原生实现的高效。。。一度怀疑我自己的手机是不是坏了(S8+),我自己实现的都有将近 30% 的性能提升。。。另外可以看出 OpenCV 的优化还是非常优秀的!
最后,可以将 Build Variants 设置为 release(默认为 debug)