【问题标题】:ARM NEON: Regular C code is faster than ARM Neon code in simple multiplication?ARM NEON:常规 C 代码在简单乘法中比 ARM Neon 代码更快?
【发布时间】:2021-12-19 01:11:04
【问题描述】:

我正在使用 ARM NEON 内在函数实现一个简单的数组乘法。输入是一个 uint8 数组,输出是一个 uint16 数组。但是,常规的本机代码比 NEON 优化的代码要快。谁能帮我弄清楚如何改进 NEON 代码?

我的常规代码是

    uint16_t scale_factor = 300;
    for(int i = 0; i < output_size; i++)
    {        
        out_16bit[i] = (uint16_t)(in_ptr[i] * scale_factor) ;
    }

我的 NEON 代码是

    uint16_t* out_ptr = out_16bit;
    uint8_t* in_ptr = in_8bit;
    uint16_t scale_factor = 300;

    for(int i = 0; i < out_size/16; i++)
    {
        uint8x16_t in_v0 = vld1q_u8(in_ptr);
        in_ptr += 16;

        uint16x8_t in_16_v0 = vmovl_u8(vget_low_u8(in_v0));
        uint16x8_t in_16_v1 = vmovl_u8(vget_high_u8(in_v0));

        uint16x8_t res_0 = vmulq_n_u16(in_16_v0, scale_factor);
        uint16x8_t res_1 = vmulq_n_u16(in_16_v1, scale_factor);

        // code below takes long time
        vst1q_u16(out_ptr,res_0);  
        vst1q_u16(out_ptr+8,res_1);  
        out_ptr += 16;

    }

我也做了一些分析,发现如果我注释掉vst1q_u16s 或out_ptr += 16,速度很快。但如果我保持上述两个,它会很慢。所以我猜可能是因为指针的增量正在等待vst1q_u16的完成?然后我更新了NEON代码,在vst1q_u16out_ptr+=16之间添加了一些代码,如下所示,

    uint8x16_t in_v0 = vld1q_u8(in_ptr);
    uint16x8_t in_16_v0 = vmovl_u8(vget_low_u8(in_v0));
    uint16x8_t in_16_v1 = vmovl_u8(vget_high_u8(in_v0));

    uint16x8_t res_0 = vmulq_n_u16(in_16_v0, scale_factor);
    uint16x8_t res_1 = vmulq_n_u16(in_16_v1, scale_factor);
    vst1q_u16(out_ptr,res_0);  
    vst1q_u16(out_ptr+8,res_1);  
    for(int i = 1; i < out_size/16; i++)
    {

        in_v0 = vld1q_u8(in_ptr);
        in_16_v0 = vmovl_u8(vget_low_u8(in_v0));
        in_16_v1 = vmovl_u8(vget_high_u8(in_v0));
    
        out_ptr += 16;

        res_0 = vmulq_n_u16(in_16_v0, scale_factor);
        res_1 = vmulq_n_u16(in_16_v1, scale_factor);

        vst1q_u16(out_ptr,res_0);  
        vst1q_u16(out_ptr+8,res_1);  

    
    }

但是这个改变没有奏效...请帮忙告诉我应该怎么做...谢谢。

【问题讨论】:

  • 您是否检查过您的编译器是否自动向量化了您的“常规代码”? gcc does.
  • 您在编译器中使用了哪些优化开关?
  • 这里的居民可能希望知道确切的编译器命令行,然后可能希望将代码和编译器选项带到 Godbolt 以查看编译器告诉机器做什么。你可以考虑这条路。
  • "请问如何检查编译器是否进行了自动向量化?"基本上,您阅读程序集并查看它是否包含 SIMD 指令。
  • “非常快”的情况可能会产生误导。如果您删除存储,编译器可能会发现循环实际上并没有做任何事情,并删除整个事情。同样,如果您不增加out_ptr,编译器可能会注意到只有最后一次迭代很重要,并跳过所有其他迭代。使用优化的编译器,你不能真正为每行代码计费,当然不能一一删除。

标签: arm simd intrinsics neon


【解决方案1】:

与 cmets 一样,简单的答案是自动矢量化。 我不确定clang 6,但在以Neon平台为目标时,最近的clang肯定会默认自动矢量化为Neon,并且很难在像这种乘法这样简单的事情上击败自动矢量化。也许为您的特定处理器展开最佳循环。但它很容易比自动矢量化更糟糕。 Godbolt 是一种非常好的比较方法,以及分析您的所有更改。

所有的 cmets 也都很好。

有关 Neon 内在函数最佳实践的更多文档,Arm 的 Neon microsite 提供了非常有用的信息,尤其是 Optimizing C with Neon intrinsics 上的文档。

【讨论】:

    猜你喜欢
    • 2020-08-29
    • 2019-01-18
    • 1970-01-01
    • 2012-06-25
    • 1970-01-01
    • 1970-01-01
    • 2017-07-02
    • 2011-08-09
    • 2015-03-18
    相关资源
    最近更新 更多