有效地计算 arm neon 中 8 个元素的数组的最大值

Pav*_*l P 5 c++ arm intrinsics neon

如何在 8 个字节、8 个短裤或 8 个整数的数组中找到最大元素?我可能只需要 max 元素的位置、max 元素的值,或者两者都需要。

例如

unsigned FindMax8(const uint32_t src[8]) // returns position of max element
{
    unsigned ret = 0;
    for (unsigned i=0; i<8; ++i)
    {
        if (src[i] > src[ret])
            ret = i;
    }
    return ret;
}
Run Code Online (Sandbox Code Playgroud)

-O2clang 展开循环但它不使用霓虹灯,这应该会提供不错的性能提升(因为它消除了许多依赖数据的分支?)

对于 8 个字节和 8 个 shorts 方法应该更简单,因为整个数组可以加载到单个 q 寄存器中。对于 arm64,使用 vmaxv_u16 应该更简单,但是如何使其在 32 位霓虹灯中高效?

正如 Marc 在评论中所指出的,当函数更改为返回最大值时,GCC 自动向量化器会为 neon64生成以下内容:

ldr q0, [x0, 16]
ld1r {v2.4s}, [x0]
ldr q1, [x0]
umax v0.4s, v0.4s, v2.4s
umax v0.4s, v0.4s, v1.4s
umaxv s0, v0.4s
umov w0, v0.s[0]
Run Code Online (Sandbox Code Playgroud)

我有一个函数可以进行相当复杂的数学运算,在计算结束时我最终得到uint32x4_t res结果,我需要的只是获取最大元素的索引。此单件是代码的最慢的部分,通过远低于这个数学重函数的其余部分的其余部分慢。

我尝试了三种不同的方法(根据分析器从最慢到最快):

  • 使用neon 进行完整计算,最终单个32 位结果从neon 传输到arm。

  • vst1q_u32(src, res) 然后使用常规 C 代码查找最大元素的索引。

  • 使用 vget_lane_u64 两次 vmov 到四个 32 位 arm 寄存器,然后使用一些位移来计算最大元素的索引。

这是我能够获得的最快版本

unsigned compute(unsigned short *input)
{
    uint32x4_t result = vld1q_u32((uint32_t*)(input));
    // some computations...
    // ... and at the end I end up with res01 and res23
    // and I need to get index of max element from them:
    uint32x2_t res01 = vget_low_u32(result);
    uint32x2_t res23 = vget_high_u32(result);

    // real code below:
    uint64_t xres01 = vget_lane_u64(vreinterpret_u64_u32(res01), 0);
    uint64_t xres23 = vget_lane_u64(vreinterpret_u64_u32(res23), 0);
    unsigned ret = 0;
    uint32_t xmax0 = (uint32_t)(xres01 & 0xffffffff);
    uint32_t xmax1 = (uint32_t)(xres01 >> 32);
    uint32_t xmax2 = (uint32_t)(xres23 & 0xffffffff);
    uint32_t xmax3 = (uint32_t)(xres23 >> 32);
    if (xmax1 > xmax0)
    {
        xmax0 = xmax1;
        ret = 1;
    }
    if (xmax2 > xmax0)
    {
        xmax0 = xmax2;
        ret = 2;
    }
    if (xmax3 > xmax0)
        ret = 3;
    return ret;
}
Run Code Online (Sandbox Code Playgroud)

使用完整霓虹灯计算的版本这样做:

  • 使用 vmax/vpmax 查找最大元素
  • 将 u32x4_t 设置为最大元素
  • 使用 vceq 将最大元素设置为 0xffffffff
  • 用 with 加载 u32x4_t 掩码 {1u<<31, 1u<<30, 1u<<29, 1u<<28 }
  • 戴面具做范德
  • 成对添加或 vorr 将所有 4 个值折叠为一个。
  • 使用 vclz 将所有设置为最大元素的索引

也许其他地方的问题,请参阅我正在尝试优化的实际代码我的优化版本,只有最后一块需要改进。不知何故,分析器显示 80% 的时间花在我计算最大索引的最后几行。有任何想法吗?将这个简单的 c-loop 更改为成对的 regs 将整个功能提高了 20-30%。请注意,根据分析器,两个 vst1_u32 是函数花费大部分时间的那些。

我可以尝试什么其他方法?

更新: 函数末尾的减速似乎与代码无关。我不知道为什么,但是当我尝试根据我调用它们的顺序运行不同版本的函数时,我得到了 3-4 倍的时间变化。此外,通过不同的测试,如果函数结束时没有停顿,那么完整的霓虹灯版本似乎是最快的,我不确定为什么会发生这种停顿。出于这个原因,我创建了一个新问题来找出原因。