关于NEON优化-1比特转8比特

1. 概述

假设原始图像为1bit,即一个字节数据可以表示8个像素的状态[0,1],如下图所示,图像的前两个字节可以表示图像的16个像素信息。
原始图像数据结构
现在需要将1bit图像转换为8bit图像,即将原始图像的每个字节拆分为8个字节,数据范围为[0,255]。如下图所示,将上图中的两个字节拆分为如下形式:
原始图像拆分

2. 代码实现

2.1. C 语言

上述功能可以通过C语言实现如下:

for (i = 0; i < IMG_SIZE; i++)
{
	data_8bit[i]  = ((data_1bit[i/8] >> (i % 8)) & 0x01) << 7;
}

运行时间大概为10.2ms。

2.2. NEON加速

上述功能可以使用NEON进行加速,思路如下:

  1. 加载操作
    使用VLD1指令从内存空间加载图像数据到Q6寄存器,该指令可以一次加载16个字节数据,对应图像的128个像素值;
    从内存加载数据进寄存器

  2. 位与操作
    使用VAND指令,对Q6Q4寄存器的元素进行位与操作,并将结果存入Q7寄存器,其中Q4寄存器可使用DUP指令全部置为0x01;此步骤将每个元素的低位像素值取出放入Q7寄存器中;
    将寄存器数据与1位与操作

  3. 比较操作
    使用VCGT指令对Q7Q5寄存器的元素逐个进行比较,判断Q7寄存器的元素是否大于对应Q5中的元素,其计算结果保存在Q8寄存器中。如果Q7的元素大于Q5的元素,则对应Q8元素设置为255,否则设置为0
    在这里插入图片描述

  4. 移位操作
    使用VSHR指令,将Q6中的元素整体向右移动1比特位,即将下一组像素移到元素最低位,方便循环取出图像像素值。
    在这里插入图片描述

  5. 循环操作
    循环上述2、3、4步骤,将Q6寄存器中的像素值分别取出,放入Q8~Q15寄存器中。
    在这里插入图片描述

  6. 打包操作
    使用VZIP指令每隔4个Q寄存器进行一次打包操作,如下图所示,使像素索引号间隔4排列。该步骤的目的是未了方便下一步的存储操作。
    在这里插入图片描述

  7. 存储操作
    使用VST4指令将上述打包好的数据,按照像素循序存储相应的内存地址。
    在这里插入图片描述

2.2.1. NEON Intrinsics

按照上述的算法思路,使用NEON Intrinsics编写上述C代码的功能,详细代码如下:

void NfsOneBitToU8(uint8_t* restrict input, uint8_t* restrict output, uint32_t size) {
  // neon for speed
  uint8x16_t v_zero = vdupq_n_u8(0x00);
  uint8x16_t v_one = vdupq_n_u8(0x01);
  uint8x16_t v_and_result;
  uint8x16x4_t result_array1, result_array2;
  uint8x16x4_t zip_out1, zip_out2;

  for (int32_t i = 0; (i + 15) < size; i += 16) {
    uint8x16_t v_input = vld1q_u8(input + i);
    for (int j = 0; j < 4; j++) {
      v_and_result = vandq_u8(v_input, v_one);  // v & 0x01
      result_array1.val[j] = vcgtq_u8(v_and_result, v_zero);
      v_input = vshrq_n_u8(v_input, 1);
    }
    for (int j = 0; j < 4; j++) {
      v_and_result = vandq_u8(v_input, v_one);  // v & 0x01
      result_array2.val[j] = vcgtq_u8(v_and_result, v_zero);
      v_input = vshrq_n_u8(v_input, 1);
    }

    uint8x16x2_t zip_temp1 = vzipq_u8(result_array1.val[0], result_array2.val[0]);
    uint8x16x2_t zip_temp2 = vzipq_u8(result_array1.val[1], result_array2.val[1]);
    uint8x16x2_t zip_temp3 = vzipq_u8(result_array1.val[2], result_array2.val[2]);
    uint8x16x2_t zip_temp4 = vzipq_u8(result_array1.val[3], result_array2.val[3]);

    zip_out1.val[0] = zip_temp1.val[0];
    zip_out1.val[1] = zip_temp2.val[0];
    zip_out1.val[2] = zip_temp3.val[0];
    zip_out1.val[3] = zip_temp4.val[0];

    zip_out2.val[0] = zip_temp1.val[1];
    zip_out2.val[1] = zip_temp2.val[1];
    zip_out2.val[2] = zip_temp3.val[1];
    zip_out2.val[3] = zip_temp4.val[1];

    vst4q_u8(output + i * 8, zip_out1);
    vst4q_u8(output + i * 8 + 64, zip_out2);
  }
}
2.2.2 NEON Assembly

同理,使用NEON Assembly重新编写上述代码,详细如下:

void NfsOneBitToU8Asm(uint8_t* restrict input, uint8_t* restrict output, uint32_t size) {
  uint8_t* input_temp = input;
  uint8_t* output_temp = output;
  uint32_t size_tmp = size;

  asm volatile("vmov.i8      q4, #0x01         \n"
               "vmov.i8      q5, #0x00         \n"
               "100:                           \n"
               "vld1.8      {q6}, [%0]!        \n"
               "pld         [%0, #128]         \n"
               "vshr.u8     q9, q6, #1         \n"
               "vshr.u8     q10, q6, #2        \n"
               "vshr.u8     q11, q6, #3        \n"
               "vshr.u8     q12, q6, #4        \n"
               "vshr.u8     q13, q6, #5        \n"
               "vshr.u8     q14, q6, #6        \n"
               "vshr.u8     q15, q6, #7        \n"
               "vand.i8     q8, q6, q4         \n"
               "vand.i8     q9, q9, q4         \n"
               "vand.i8     q10, q10, q4       \n"
               "vand.i8     q11, q11, q4       \n"
               "vand.i8     q12, q12, q4       \n"
               "vand.i8     q13, q13, q4       \n"
               "vand.i8     q14, q14, q4       \n"
               "vand.i8     q15, q15, q4       \n"
               "vcgt.s8     q8, q8, q5         \n"
               "vcgt.s8     q9, q9, q5         \n"
               "vcgt.s8     q10, q10, q5       \n"
               "vcgt.s8     q11, q11, q5       \n"
               "vcgt.s8     q12, q12, q5       \n"
               "vcgt.s8     q13, q13, q5       \n"
               "vcgt.s8     q14, q14, q5       \n"
               "vcgt.s8     q15, q15, q5       \n"
               "vzip.8      q8, q12            \n"
               "vzip.8      q9, q13            \n"
               "vzip.8      q10, q14           \n"
               "vzip.8      q11, q15           \n"
               "vst4.8      {d16, d18, d20, d22}, [%1]! \n"
               "vst4.8      {d17, d19, d21, d23}, [%1]! \n"
               "vst4.8      {d24, d26, d28, d30}, [%1]! \n"
               "vst4.8      {d25, d27, d29, d31}, [%1]! \n"
               "subs        %2, #16             \n"
               "bgt         100b                \n"
               : "=r"(input_temp), "=r"(output_temp), "=r"(size_tmp)
               : "0"(input_temp), "1"(output_temp), "2"(size_tmp)
               : "memory",
                 "cc",
                 "q4",
                 "q5",
                 "q6",
                 "q7",
                 "q8",
                 "q9",
                 "q10",
                 "q11",
                 "q12",
                 "q13",
                 "q14",
                 "q15");
}

2.3.性能对比

如下表所示,上述代码分别在某A9平台运行性能如下:

FunctionC languageIntrinsicsAssembly
NfsOneBitToU810.2 ms2.13 ms1.78 ms

3. 结论

通过上述列子可以看出,使用NEON对算法进行加速效果明显,使用NEON Intrinsics已经提升了很多性能,而且该方式方便移植,编译器也会对指令进行重排。当然为了进一步提升性能可以使用汇编语言编写上述NEON代码,性能可以进一步提升,但提升的程度相对较小。

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

henry.zhu51

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值