Neon 在 Intrinsics 中的校验和代码实现

Posted

技术标签:

【中文标题】Neon 在 Intrinsics 中的校验和代码实现【英文标题】:Checksum code implementation for Neon in Intrinsics 【发布时间】:2012-08-22 05:46:20 【问题描述】:

我正在尝试使用内在函数实现 NEON 的校验和计算代码(2 的补码加法)。当前的校验和计算是在 ARM 上进行的。

我的实现一次从内存中取出 128 位到 NEON 寄存器中并执行 SIMD(加法),结果从 128 位数字折叠为 16 位数字。

看起来一切正常,但我的 NEON 实现比 ARM 版本花费的时间更多。

ARM 版本耗时:0.860000 s NEON 版本耗时:1.260000 s

注意:

    使用“time.h”中的实用程序进行分析 从示例应用程序中调用了 10,000 次校验和函数,并在所有函数运行完成后计算时间

其他详情:

    使用 GNU 工具链 (arm-none-linux-gnueabi-gcc) 编译内部代码,而不是 arm 工具链。 Linux 平台。 C 内部代码。

问题:

    为什么 NEON 版本比 ARM 版本需要更多时间? (虽然我已经注意使用批处理中最小循环的内在函数)

    如何实现我想要实现的目标? (NEON 的效率)

    谁能指点我或分享一些使用ARM-NEON互操作的示例实现(伪代码/算法/代码,而不是理论实现论文或演讲)?

任何帮助将不胜感激。

这是我的代码:

uint16_t do_csum(const unsigned char * buff, int len)

int odd, count, i;

uint32x4_t result = veorq_u32( result, result), sum = veorq_u32( sum, sum); 
uint16x4_t data, data_hi, data_low, data8;
uint16x8_t dataq;
uint16_t result16, disp[20] = 0,0,0,0,0,0,0,0,0,0;

if (len <= 0)
    goto out;
odd = 1 & (unsigned long) buff;
if (odd) 
    uint8x8_t data1 = veor_u8( data1, data1); 
    data1 = (uint16x4_t)vld1_lane_u8((uint8_t *)buff, data1, 0); //result = *buff << 8;
    data1 = (uint16x4_t)vshl_n_u16( data1, 8);

    len--;
    buff++;
    result = vaddw_u16(result, data1);

count = len >> 1;       /* nr of 16-bit words.. */
if (count) 
    if (2 & (unsigned long) buff) 
        uint16x4_t data2 = veor_u16( data2, data2); 
        data2 = (uint16x4_t) vld1_lane_u16((uint16_t *)buff, data2, 0); //result += *(unsigned short *) buff;
        count--;
        len -= 2;
        buff += 2;
        result = vaddw_u16( result, data2);
    
    count >>= 1;        /* nr of 32-bit words.. */
    if (count) 
        if (4 & (unsigned long) buff) 
            uint32x2_t data4 = (uint16x4_t) vld1_lane_u32((uint32_t *) buff, data4, 0);
            count--;
            len -= 4;
            buff += 4;
            result = vaddw_u16( result, data4);
        
        count >>= 1;    /* nr of 64-bit words.. */
        if (count) 
            if (8 & (unsigned long) buff) 
                uint64x1_t data8 = vld1_u64((uint64_t *) buff); 
                count--;
                len -= 8;
                buff += 8;
                result = vaddw_u16( result,(uint16x4_t)data8);
            
            count >>= 1;    /* nr of 128-bit words.. */
            if (count) 
                do 
                    dataq = (uint16x8_t)vld1q_u64((uint64_t *) buff); // VLD1.64 d0, d1, [r0]
                    count--;
                    buff += 16;

                    sum = vpaddlq_u16(dataq);   
                    vst1q_u16( disp, dataq); // VST1.16 d0, d1, [r0]

                    result = vaddq_u32( sum, result);
                 while (count);
            
            if (len & 8) 
                uint64x1_t data8 =  vld1_u64((uint64_t *) buff); 
                buff += 8;
                result = vaddw_u16( result, (uint16x4_t)data8);
            
        
        if (len & 4) 
            uint32x2_t data4 = veor_u32( data4, data4); 

            data4 = (uint16x4_t)vld1_lane_u32((uint32_t *) buff, data4, 0);//result += *(unsigned int *) buff;
            buff += 4;
            result = vaddw_u16( result,(uint16x4_t) data4);
        
    
    if (len & 2) 
        uint16x4_t data2 = veor_u16( data2, data2); 
        data2 = (uint16x4_t) vld1_lane_u16((uint16_t *)buff, data2, 0); //result += *(unsigned short *) buff;
        buff += 2;
        result = vaddw_u16( result, data2);
    

if (len & 1)
    uint8x8_t data1 = veor_u8( data1, data1); 
    data1 = (uint16x4_t) vld1_lane_u8((uint8_t *)buff, data1, 0); //result = *buff << 8;
    result = vaddw_u8( result, data1);



result16 = from128to16(result);

if (odd)
    result16 = ((result16 >> 8) & 0xff) | ((result16 & 0xff) << 8);

out:
    return result16;

【问题讨论】:

显示你的代码,我会告诉你它有什么问题。你在使用 GCC 吗?如果是这样,我建议在单独的文件中编写汇编语言或使用内联 asm,因为 GCC 不能很好地处理内在函数。 @BitBank:谢谢,已经编辑了我的问题以包含代码,是的,我正在使用交叉编译器 gcc。使用内在,因为我几乎没有准备好进入装配的浅水区。 你在测试中使用len 的什么值?另外,你是用-O3 编译的吗? 感谢@Paul R 的编辑,1. 长度为 2k 字节(数据从文件读取到数组到应用程序中,然后传递给 do_sum 函数)。 2.我正在使用以下命令进行编译:arm-none-linux-gnueabi-gcc -mfpu=neon -mfloat-abi=softfp -flax-vector-conversions -c 不使用任何级别(老实说, 不知道任何与关卡有关的事情)。 真的需要gcc -O3 ...来启用编译器优化。 【参考方案1】:

一些你可以改进的地方:

摆脱商店到disp - 这看起来像是留在里面的调试代码? 不要在主循环中进行水平加法 - 只需在循环中进行部分(垂直)求和,然后在循环后进行最后一次水平加法(有关如何执行此操作的示例,请参阅 this answer - 适用于 SSE但原理是一样的) 确保您使用gcc -O3 ... 以从编译器优化中获得最大收益 不要使用goto! (不影响性能,但有害。)

【讨论】:

1.显示代码确实是调试代码,我将其注释掉,在这里被遗漏了,对此感到抱歉。 2. 能不能多点启发? 3. 考虑完成。 使用了你建议的选项,:arm-none-linux-gnueabi-gcc -03 -mfpu=neon -mfloat-abi=softfp -flax-vector-conversions -c neonChecksum.c,但是编译器抛出错误:arm-none-linux-gnueabi-gcc: unrecognized option '-03' 对不起,你的意思是O3(字母O,数字3),看起来像03(数字0,数字3),对不起上面的评论,现在编译好了,很快就会用我的发现更新你。 哇...!!!它工作得非常快..!!现在花费的时间是:0.050000 s..!! 16X 优于 ARM,24X 优于未使用选项 -O3.. 优化的 NEON 代码!谢谢@Paul R。我已经准备好接受这个答案,如果你能回答我的主要问题中列出的其他问题。 4X 还不错 - 如果您准备花大量时间编写和手动优化 NEON asm,您可以做得更好,但如果您可以通过使用内部函数的相当简单的实现来满足您的性能目标上面然后对此感到满意。

以上是关于Neon 在 Intrinsics 中的校验和代码实现的主要内容,如果未能解决你的问题,请参考以下文章

NEON Intrinsics 练习题

NEON指南-4-Neon intrinsics chromium case study

Neon Intrinsics各函数介绍

Neon Intrinsics各函数介绍

ARM NEON Intrinsics:将向量的值限制为 0-255

Neon intrinsics 简明教程