如何将arm64中vaddv_u8的结果视为霓虹灯寄存器

时间:2018-04-25 17:48:27

标签: c++ arm intrinsics neon arm64

来自AArch64(arm64)的

vaddv_u8和其他类似的新v-intrinsics返回uint8_t。如何将此内在结果作为氖寄存器而不是普通C类型处理?

For example

void paddClz(uint8_t* x)
{
    uint8x8_t ret = vdup_n_u8(0);
    for (int i = 0; i < 8; ++i, x += 8)
    {
        uint8x8_t x8 = vld1_u8(x);
        uint8_t sum = vaddv_u8(x8);
        uint8x8_t r = vdup_n_u8(sum); //or: r = vset_lane_u8(sum, r, 0);
        r = vclz_u8(r);
        ret = vext_u8(ret, r, 1);
    }
    vst1_u8(x, ret);
}

铿锵产生了什么:

paddClz(unsigned char*): // @paddClz(unsigned char*)
  mov x8, xzr
  movi d0, #0000000000000000
.LBB0_1: // =>This Inner Loop Header: Depth=1
  ldr d1, [x0, x8]
  add x8, x8, #8 // =8
  cmp w8, #64 // =64
  addv b1, v1.8b
  dup v1.8b, v1.b[0]   <<== useless! I only need/use/care about v1.b[0]
  clz v1.8b, v1.8b
  ext v0.8b, v0.8b, v1.8b, #1
  b.ne .LBB0_1
  str d0, [x0, #64]
  ret

正如您所看到的,将dup结果转换为可用作uint8_t vaddv_u8参数的类型需要一个无用的vclz_u8内在函数。我只从后续的vclz_u8结果中取出第一条线,所以实际上将它复制到所有通道都会浪费工作。

如何在内在函数中编写它以在氖类型变量中获取sum而不使编译器发出无用的操作码? (并且最好在源代码中没有这些额外的噪声。)如果不是这样,那就明确而且明显:我不是要求优化或改进我发布的那段代码;我只是写它来表明问题。

3 个答案:

答案 0 :(得分:2)

你应该得到一个带有有序SoC的测试设备。 Apple的A系列芯片都是无序的,到目前为止是最精确的芯片。

您的iPhone上的实施可能会运行得足够快,但几乎不会比有序内核上最简单的C版本快,直接无法使用。

在您急于在NEON上编写循环之前请三思。 你可以通过转置矩阵在大多数情况下完全避免所谓的“水平”操作,然后进行“垂直”数学运算。

#define vuzp8(a, b, c) ({ \
    c = vuzp_u8(a, b); \
    a = c.val[0]; \
    b = c.val[1]; \
})

void foo(uint8_t *pDst, uint8_t *pSrc)
{
    uint8x8x4_t top, bottom;
    uint8x8x2_t temp;

    top = vld4_u8(pSrc);
    pSrc += 32;
    bottom = vld4_u8(pSrc);

    vuzp8(top.val[0], bottom.val[0], temp);
    vuzp8(top.val[1], bottom.val[1], temp);
    vuzp8(top.val[2], bottom.val[2], temp);
    vuzp8(top.val[3], bottom.val[3], temp);

    top.val[0] += bottom.val[0];
    top.val[1] += bottom.val[1];
    top.val[2] += bottom.val[2];
    top.val[3] += bottom.val[3];

    top.val[0] += top.val[1];
    top.val[2] += top.val[3];

    top.val[0] += top.val[2];

    top.val[0] = vclz_u8(top.val[0]);

    vst1_u8(pDst, top.val[0]);
}

另一个例子,你问自己intrinsux是否有意义。它的笨拙使得代码变得更加复杂,并且它不足以表达三个128位加一个64位加法而不是六个64位加法。

此外,你必须再次检查编译器是否没有弄乱任何东西,特别是当你进行排列时(vzip, vuzp, vtrn

我认为机器代码在aarch32上可以正常使用,但我对aarch64不太确定,其中排列说明有很大不同。

我认为你现在明白我为什么讨厌害虫intrinsux。它比任何帮助都更令人讨厌。

PS: Teclast P10 Android平板电脑是aarch64测试设备的绝佳选择:所有八个核心都相同,安装了Android 7.12 64位,只需花费大约100美元。

答案 1 :(得分:1)

似乎I can do this in clang

int paddClz(const uint8_t* x)
{
    uint8x8_t x8 = vld1_u8(x);
    uint8_t sum = vaddv_u8(x8);
    uint8x8_t r;
    r = vset_lane_u8(sum, r, 0);
    r = vclz_u8(r);
    return vget_lane_u8(r, 0);
}

这正是我想要的:

addv b0, v0.8b
clz v0.8b, v0.8b

但是,该代码中的gcc produces some mess。另一个问题是它使用未初始化的r,并且根据您设置构建的方式,它可能是不可接受的。更重要的是,它似乎不适用于更复杂的场景。有没有更好/更合适的方法呢?

答案 2 :(得分:1)

您的解决方法可能会使性能变差。你的问题就好像你需要uint8_t的单个向量的标量结果一样。返回标量值的vaddv_u8指令没有任何问题。在ARMv8上,“NEON单元”现在已完全集成,并且在NEON和ARM寄存器之间移动数据时没有太大的代价。只需使用C内在函数来计算结果的前导零,您就可以得到所需的内容:

int paddClz(const uint8_t* x)
{
    uint8x8_t x8 = vld1_u8(x);
    uint8_t sum = vaddv_u8(x8);
    return __builtin_clz(sum) - 24;
}

intrisic将被编译到单个ARM指令(CLZ)中。

如果您正在使用更大的数据集,请编写C代码以正确反映这一事实。