海思3559平台验证neon优化rgb转灰度图算法

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 
#include 
#include 
#include 
#include 
#include 

void rgb2gray (char* dest, char* src, int n)    //未使用neon优化的算法
{
  int i;
  for (i=0; iint 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//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平台上测试,测试结果如下
算法运行时间

你可能感兴趣的:(arm,neon,嵌入式)