搭建实验环境
首先新建一个包含native代码的项目:
然后在gradle中添加对neon的支持:
externalNativeBuild {
cmake {
cppFlags "-std=c++14"
arguments "-DANDROID_ARM_NEON=TRUE"
}
}
这样,项目就可以支持neon加速了。
小试牛刀
一个最简单的neon编程的流程大致是这样的:
1、装载数据到neon寄存器
2、执行运算
3、从neon寄存器中把结果写回内存。
没有例子不知从何说起,先上一个超级简单的例子吧:
#include <jni.h>
#include <string>
#include <arm_neon.h>
#include <android/log.h>
#define LOG_TAG "TEST_NEON"
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)
extern "C"{
void test()
{
int16_t result[8];
int8x8_t a = vdup_n_s8(121);
int8x8_t b = vdup_n_s8(2);
int16x8_t c;
c = vmull_s8(a,b);
vst1q_s16(result,c);
for(int i=0;i<8;i++){
LOGD("data[%d] is %d ",i,result[i]);
}
}
JNIEXPORT jstring
JNICALL
Java_com_example_javer_myapplication_MainActivity_stringFromJNI(
JNIEnv *env,
jobject /* this */) {
std::string hello = "Hello from C++";
test();
return env->NewStringUTF(hello.c_str());
}
}
执行结果:
09-07 12:03:08.335 11709-11709/? D/TEST_NEON:
data[0] is 242
data[1] is 242
data[2] is 242
data[3] is 242
data[4] is 242
data[5] is 242
data[6] is 242
data[7] is 242
代码中,test函数中实现了两个64位neon寄存器的乘法。
vdup是数据复制指令,这里把128这个8位的数复制到一个64位的寄存器中,64位能存放8个8位的数,因此,此时a指向的neon寄存器存放了8个128。两个8位的数相乘,结果可能是16位的,因此,结果需要用一个128位的寄存器来保存。int16x8就表示的是一个128位的寄存器。
vmull_s8把a,b相乘,并将结果保存在c中。c指向的是neon的128位寄存器,因此,我们需要把结果写回内存。
vst1q_s16把c中的数据协会result指向的内存中。
这是一个简单的测试neon指令的代码,通过这个代码我们能清晰的认识到neon加速的原理:一次装载8个8位的数到64位寄存器,一条指令能把实现两个8*8的数据块的乘法。这样效率不就接近提升8倍么?当然没有这么理想,毕竟装载数据和写回数据也是需要时间的。
实战尝试
接下来,尝试一个比较简单的rgb转灰度图的code:
void normal_convert (uint8_t * __restrict dest, uint8_t * __restrict src, int n)
{
int i;
for (i=0; i<n; i++)
{
int r = *src++; // load red
int g = *src++; // load green
int b = *src++; // load blue
// build weighted average:
int y = (r*77)+(g*151)+(b*28);
// undo the scale by 256 and write to memory:
*dest++ = (y>>8);
}
}
void neon_convert (uint8_t * __restrict dest, uint8_t * __restrict src, int n)
{
int i;
uint8x8_t rfac = vdup_n_u8 (77);
uint8x8_t gfac = vdup_n_u8 (151);
uint8x8_t bfac = vdup_n_u8 (28);
n/=8;
for (i=0; i<n; i