使用NEON主要有四种方法:
根据优化程度需求不同,第4种最为底层,若熟练掌握效果最佳,一般也会配合第3种一起使用。本文将会重点介绍第3、4种方法。先简要介绍前两种。
-ftree-vectorize
。提供了一个连接NEON操作的C函数接口,编译器会自动生成相关的NEON指令,支持ARMv7-A或ARMv8-A平台。
所有的intrinsics函数都在GNU官方说明文档。
一个简单的例子:
//add for int array. assumed that count is multiple of 4
#include<arm_neon.h>
// C version
void add_int_c(int* dst, int* src1, int* src2, int count)
{
int i;
for (i = 0; i < count; i++)
dst[i] = src1[i] + src2[i];
}
}
// NEON version
void add_float_neon1(int* dst, int* src1, int* src2, int count)
{
int i;
for (i = 0; i < count; i += 4)
{
int32x4_t in1, in2, out;
in1 = vld1q_s32(src1);
src1 += 4;
in2 = vld1q_s32(src2);
src2 += 4;
out = vaddq_s32(in1, in2);
vst1q_s32(dst, out);
dst += 4;
}
}
代码中的vld1q_s32
会被编译器转换成vld1.32 {d0, d1}, [r0]
指令,同理vaddq_s32
和vst1q_s32
被转换成vadd.i32 q0, q0, q0
,vst1.32 {d0, d1}, [r0]
。若不清楚指令意义,请参见ARM® Compiler armasm User Guide - Chapter 12 NEON and VFP Instructions。
NEON可以有两种写法:
比如上述intrinsics代码产生的汇编代码为:
// ARMv7-A/AArch32
void add_float_neon2(int* dst, int* src1, int* src2, int count)
{
asm volatile (
"1: \n"
"vld1.32 {q0}, [%[src1]]! \n"
"vld1.32 {q1}, [%[src2]]! \n"
"vadd.f32 q0, q0, q1 \n"
"subs %[count], %[count], #4 \n"
"vst1.32 {q0}, [%[dst]]! \n"
"bgt 1b \n"
: [dst] "+r" (dst)
: [src1] "r" (src1), [src2] "r" (src2), [count] "r" (count)
: "memory", "q0", "q1"
);
}
笔者在前段时间连续使用NEON做ARM平台的优化,由于中文资料少得可怜,且英文资料零散琐碎,期间也遇到不少坑,先摘出部分经验至此,希望能够帮助到大家。︿( ̄︶ ̄)︿
理清所需的寄存器、指令。 建议根据要实现的任务,画出数据变换流程,和每步所需的具体指令,尽可能找到最优的实现流程。这一步非常关键,如果思路出错或是不够优化,则会影响使用NEON的效果,并且对程序修改带来麻烦,一定要找到最优的实现算法哦~
先实现intrinsics(可选)。 初学者先实现intrinsics是有好处的,字面理解性更强,且有助于理解NEON指令。建议随时打印关键步骤的数据,以检查程序的正误。
写成汇编进一步优化。 将intrinsics生成的汇编代码进行优化调整。一般来说,有以下几点值得注意【干货】:
【注意】在此笔者温馨提示各位看官(⊙o⊙)不仅是NEON,所有的性能优化是个经验活儿,需要自己动手才能领悟更多的诀窍,总结一下NEON优化就是:
对NEON优化使用的好坏直接导致优化效果,优化效果好的会节省70%以上的时间。
当读者熟练后就可以直接上手内联汇编了。时间有限,本文中不具体介绍inline assembly的使用方法,我后续可能会将这部分单独写成一篇博客。感兴趣者请参见ARM GCC Inline Assembler Cookbook
一些使用心得:
asm volatile (
... /* assembly code */
: "+r"(arg0) // %0
"+r"(arg1) // %1 // Output Registers
: "r"(arg2) // %2 // Input Registers
: "cc", "memory", r0, r1
);
can't find a register in class 'GENERAL_REGS' while reloading 'asm'
错误。