vaddv_u8
和其他类似的新v-intrinsics返回uint8_t
。如何将此内在结果作为氖寄存器而不是普通C类型处理?
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
而不使编译器发出无用的操作码? (并且最好在源代码中没有这些额外的噪声。)如果不是这样,那就明确而且明显:我不是要求优化或改进我发布的那段代码;我只是写它来表明问题。
答案 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)
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代码以正确反映这一事实。