NEON简介
NEON就是一种基于SIMD思想的ARM技术,相比于ARMv6或之前的架构,NEON结合了64-bit和128-bit的SIMD指令集,提供128-bit宽的向量运算(vector operations)。NEON技术从ARMv7开始被采用,目前可以在ARM Cortex-A和Cortex-R系列处理器中采用。NEON包含16个128位寄存器,拥有100多条完整指令,并且拥有独立的寄存器系统和独立的硬件执行单元,支持8位、16位、32位、64位等数据类型的向量运算,最多可同时对16路8位数据进行并行计算,可用于2D/3D图形图像加速、音视频编解码、数字信号处理等应用。
使用NEON主要有四种方法:
1. NEON优化库(Optimized libraries)
2. 向量化编译器(Vectorizing compilers)
3. NEON 内联函数(intrinsics)
4. NEON 汇编(assembly)
PPM格式图片介绍:http://www.jianshu.com/p/e809269b4ad7
rgb转灰度图公式: y = (r*77)+(g*151)+(b*28);
程序利用NEON内联函数和内嵌NEON汇编进行rgb转灰度算法优化,,参考代码为http://hilbert-space.de/?p=22
为了简易,以PPM格式图片作为样例,测试平台为海思3559
代码(初学,故加了大量注释):
/*
读入一个256*256像素的PPM图片,分别用C语言、
neon内联函数、neon汇编优化实现算法进行rgb转灰度图
公式:y = (r*77)+(g*151)+(b*28);
用gettimeofday函数测试算法运行时间
*/
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <string.h>
#include <sys/time.h>
#include <arm_neon.h>
void rgb2gray (char* dest, char* src, int n) //未使用neon优化的算法
{
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;
dest++;
}
}
void neon_rgb2gray (char* dest, char* src, int n) //使用内联neon函数优化后的算法
{
int i;
uint8x8_t rfac = vdup_n_u8 (77); //将77(8位)存入rfac中(8位*8,即存入8个77)
uint8x8_t gfac = vdup_n_u8 (151);
uint8x8_t bfac = vdup_n_u8 (28);
n/=8;
for (i=0; i<n; i++)
{
uint16x8_t temp; //uint16位*8
uint8x8x3_t rgb = vld3_u8 (src); //效果等同 vld3.8 {d0-d2}, [r1]!
uint8x8_t result;
temp = vmull_u8 (rgb.val[0], rfac); //长向量乘法
temp = vmlal_u8 (temp,rgb.val[1], gfac);
temp = vmlal_u8 (temp,rgb.val[2], bfac);
result = vshrn_n_u16 (temp, 8); //右移
vst1_u8 (dest, result); //存储
src += 8*3;
dest += 8;
}
}
void asm_rgb2gray (char* dest, char* src, int n) //这一函数需要编译成汇编后替换成汇编优化的neon代码
{
asm volatile
(
"# r0: Ptr to destination data\t\n" //GCC 编译出来的代码,参数传递小于 4 个,是通过 r0~r3 传递的,从第 5 个开始,都是通过堆栈传递的
"# r1: Ptr to source data\t\n"
"# r2: Iteration count:\t\n"
// "push {r4-r5,lr}\t\n" //保存r4,r5,lr到栈,lr: 连接返回寄存器,保留函数返回后,下一条应执行的指令
"lsr r2, r2, #3\t\n" //r2右移3位,r2/8,n/8即为循环次数
"# build the three constants:\t\n"
"mov r3, #77\t\n" //r3=77
"mov r4, #151\t\n" //r4=151
"mov r5, #28\t\n" //r5=28
"vdup.8 d3, r3\t\n" //将r3寄存器的值赋到d3寄存器的每个8位,d寄存器有64位
"vdup.8 d4, r4\t\n" //将r4寄存器的值赋到d4寄存器的每个8位
"vdup.8 d5, r5\t\n" //将r5寄存器的值赋到d5寄存器的每个8位
".loop:\t\n" //进入循环
"# load 8 pixels:\t\n"
"vld3.8 {d0-d2}, [r1]!\t\n" //将r1寄存器里的值按8位拷贝进d0,d1,d2,[r1]!表示根据寻址规则修改寄存器,然后根据寄存器中的值访问内存
//在加载或者存储后更新指针,并准备好加载或存储下一个元素。指针的增量等于该指令读取或者写入的字节个数
"# do the weight average:\t\n"
"vmull.u8 q3, d0, d3\t\n" //l,长指令,双倍长度,d0*d3(8位),结果存入q3(16位),q为128位寄存器,可同时操作8个数据
"vmlal.u8 q3, d1, d4\t\n"
"vmlal.u8 q3, d2, d5\t\n"
"# shift and store:\t\n"
"vshrn.u16 d6, q3, #8\t\n" //q3右移8位,存入d6
"vst1.8 {d6}, [r0]!\t\n" //d6按8位存入r0(指针)指向的内存,更新r0(指针)
"subs r2, r2, #1\t\n" //r2减一,并设置标志位
"bne .loop\t\n" //判定,不为1跳至.loop
// "pop { r4-r5, pc }\t\n"
);
}
void ppm_read(char* ppm,char* filename,int w,int h) //PPM图片数据读取入数组
{
FILE *fp=fopen(filename,"rb");
char ppmhead[20];
fgets(ppmhead,20,fp); //ppm前3行为头信息,第一行为编码格式,p6
// printf("%s\n",ppmhead);
fgets(ppmhead,20,fp); //第二行为宽高值,256 256
// printf("%s\n",ppmhead);
fgets(ppmhead,20,fp); //第三行为最大像素值 255
// printf("%s\n",ppmhead);
fread(ppm,w*h*3,1,fp);
fclose(fp);
}
void pgm_write(char* pgm,char* filename,int w,int h) //将灰度图保存成pgm图片
{
char writehead[20];
sprintf(writehead,"P5\n%d %d\n255\n",w,h); //头信息,与PPM相比,就第一行编码格式不同,为P5
FILE *wfp=fopen(filename,"wb");
fwrite(writehead,strlen(writehead),1,wfp); //写入头信息
fwrite(pgm,w*h,1,wfp);
fclose(wfp);
}
int main()
{
unsigned char *ppm=(unsigned char*)malloc(256*256*3);
char*filename="lena.ppm";
int w=256;
int h=256;
struct timeval start;
struct timeval end;
unsigned long timer;
ppm_read(ppm,filename,w,h); //读入PPM图片
unsigned char *pgm_gray=(unsigned char *)malloc(256*256);
memset(pgm_gray, 0, 256*256);
gettimeofday(&start,NULL);
rgb2gray(pgm_gray,ppm,256*256); //调用未使用neon优化的算法
gettimeofday(&end,NULL);
timer = 1000000 * (end.tv_sec-start.tv_sec)+ end.tv_usec-start.tv_usec;
printf("c timer = %ld us\n",timer); //打印算法运行时间
char* pgm_filename="lena_c.pgm";
pgm_write(pgm_gray,pgm_filename,w,h); //保存灰度图
memset(pgm_gray, 0, 256*256); //将灰度数据清零
gettimeofday(&start,NULL);
neon_rgb2gray(pgm_gray,ppm,256*256); //调用联neon函数优化后的算法
gettimeofday(&end,NULL);
timer = 1000000 * (end.tv_sec-start.tv_sec)+ end.tv_usec-start.tv_usec;
printf("c_neon timer = %ld us\n",timer);
char* pgm_neon_filename="lena_neon.pgm";
pgm_write(pgm_gray,pgm_neon_filename,w,h);
memset(pgm_gray, 0, 256*256);
gettimeofday(&start,NULL);
asm_rgb2gray(pgm_gray,ppm,256*256); //调用汇编优化算法
gettimeofday(&end,NULL);
timer = 1000000 * (end.tv_sec-start.tv_sec)+ end.tv_usec-start.tv_usec;
printf("asm_neon timer = %ld us\n",timer);
char* pgm_asm_filename="lena_asm.pgm";
pgm_write(pgm_gray,pgm_asm_filename,w,h);
free(ppm);
ppm = NULL;
return 0;
}
用以下命令编译
arm-hisiv600-linux-gcc -mfloat-abi=softfp -mfpu=neon -o rgb2gray_neontest rgb2gray_neontest.c
将可执行文件和测试图片放在海思3559平台上测试,测试结果如下