NEON:优化代码

问题描述 投票:0回答:1

我目前正在使用 ARM Neon,并编写了以下函数,一个用 C 语言,一个用 NEON Intrinsics 来比较速度。这些函数比较两个数组。参数

cb
是字节数除以8:

inline uint32_t is_not_zero(uint32x4_t v)
{
        uint32x2_t tmp = vorr_u32(vget_low_u32(v), vget_high_u32(v));
        return vget_lane_u32(vpmax_u32(tmp, tmp), 0);
}

uint32_t sum_neon(const uint8_t *s1, const uint8_t *s2, uint32_t cb)
{
        const uint32_t *s1_cmp = (uint32_t *)s1;
        const uint32_t *s2_cmp = (uint32_t *)s2;

        cb *= 2;

        while (cb--)
        {
                uint32x4x2_t cmp1 = vld2q_u32(s1_cmp);
                uint32x4x2_t cmp2 = vld2q_u32(s2_cmp);

                uint32x4_t res1 = vceqq_u32(cmp1.val[0], cmp2.val[0]);
                uint32x4_t res2 = vceqq_u32(cmp1.val[1], cmp2.val[1]);

                if (!is_not_zero(res1)) return 1;
                if (!is_not_zero(res2)) return 1;

                s1_cmp += 8;
                s2_cmp += 8;
        }
        return 0;
}

uint32_t sum_c(const uint8_t *s1, const uint8_t *s2, uint32_t cb)
{
    const uint64_t *p1 = (uint64_t *)s1;
    const uint64_t *p2 = (uint64_t *)s2;
    uint32_t n = 0;
    while (cb--) {
        if ((p1[n  ] != p2[n  ]) ||
                (p1[n+1] != p2[n+1]) ||
                (p1[n+2] != p2[n+2]) ||
                (p1[n+3] != p2[n+3])) return 1;
        ++n;
    }
    return 0; 
}

我不明白为什么 C 实现比 NEON 变体快得多。该代码是在树莓派上使用编译的

-O3 -mcpu=cortex-a7 -mfpu=neon-vfpv4 -mfloat-abi=hard
作为 CFlags。

c arm neon
1个回答
0
投票

尽管存在错误(见下文),此类问题的答案可能最终取决于硬件以及您是否使用arm32 neon或arm64 neon,因为ISA是不同的。我假设我们在这里查看arm32,因为您没有使用4通道arm64 max指令,这将节省一些将uint32x4_t转换为bool/uint32_t的操作。对于arm32,我们面临着在有序处理器上运行的风险,因此管道延迟可能是一个重要因素。每条指令最多可能有 8 个周期,而您只展开了 2 个周期。此外,某些设计的向量单元运行速度比标量单元晚 10 个周期左右,因此每次从向量单元移动数据时到标量,就像你在“if (!is_not_zero(res1))...”中所做的那样,你将经历 10 个周期的停顿,或者无论延迟是什么。那可能是一个杀手。最好使用采样器来了解发生了什么,并观察样品在组装过程中的位置并解释茶叶。找到一个可以向您展示组装的采样器可能本身就是一个挑战。

最终,无论您使用的是 32 位还是 64 位 ARM,将 simd 寄存器减少到适合条件寄存器的过程在 ARM 上都是昂贵的。它们没有像 Intel 的 PTEST 或 Altivec 的点指令那样直接将数据移动到条件寄存器的指令。即使标量单元和向量单元之间没有延迟,N 条指令序列也会杀了你。你只是不能经常进行这种减少。您可以将一堆向量进行“或”运算,并且不频繁地检查“总和”中是否有任何通道不为零。

Bug?:另外,我认为你的向量线 cb *= 2 是错误的,可能应该是 cb /= 4 以纠正 uint64_t 和 uint32x4x2_t 之间的大小差异。假设你没有崩溃,这个错误将使你的时间增加四倍。另一方面,我觉得标量代码中的 ++n 也有类似的错误——应该是 n+=4 吗? ——所以也许我不完全理解你想要实现的目标。这里似乎正在进行一些多余的工作。

最新问题
© www.soinside.com 2019 - 2024. All rights reserved.