如何将 arm64 中的vaddv_u8结果视为氖寄存器
How to treat result of vaddv_u8 in arm64 as a neon register
vaddv_u8
和 AArch64 (arm64) 的其他一些类似的新 v 内联函数返回uint8_t
。如何将此内在的结果视为 neon 寄存器而不是普通 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
如您所见,需要将uint8_t vaddv_u8
结果转换为可用作vclz_u8
参数的类型,需要无用的dup
。 我只从随后的vclz_u8
结果中选取第一条车道,因此实际上将其复制到所有车道将是浪费工作。
如何在内部函数中编写它以在 neon 类型变量中获得该sum
,而不会使编译器发出无用的操作码? (最好在源代码中没有这种额外的噪音。如果不是,要清楚明了:我不是要求优化或改进我发布的那段代码;我只是写它来显示问题。
你真的应该得到一个带有有序SoC的测试设备。 苹果的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 P10Android平板电脑作为aarch64
测试设备是一个很好的候选者:所有八个内核都是一样的,安装了Android 7.12 64位,只需100美元左右。
您的解决方法可能会使性能变差。你的问题写得好像你想要从你的单个向量中获得标量结果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;
}
内部指令将被编译为单个ARM指令(CLZ)。
如果您使用的是更大的数据集,请编写 C 代码以正确反映这一事实。
似乎我可以在叮当声中做到这一点:
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 从该代码中产生了一些混乱。另一个问题是它使用未初始化的r
并且根据您设置构建的方式,它可能是不可接受的。更重要的是,它似乎不适用于更复杂的场景。有没有更好/正确的方法可以做到这一点?
- 本质:使用__128寄存器
- 将寄存器设计成可由C和C++访问的外设的最佳实践
- 在模拟器中使用并集来模拟CPU寄存器有多合适
- 使用英特尔 PIN 修改寄存器
- AVX 指令中寄存器和指针之间的客观差异
- 如何确定我的处理器有多少个 AVX 寄存器?
- 除非使用某些寄存器,否则函数挂钩会崩溃
- 寄存器上的管道计算
- 其中关于内存和寄存器的左值和右值
- 有没有办法强制C++编译器将变量存储在寄存器中?
- "变量":函数中函数作用域不允许初始化的自动或寄存器变量'naked'
- Atmel Studio:返回一个包含数组的寄存器
- 使用 googletest 测试嵌入式C++代码时处理外设寄存器的重复符号
- 移位寄存器74HC595输出电流
- 超过255的Modbus寄存器无法访问SimpleModbus
- 如何在程序集函数中将元素数组作为参数传递时转发 ARM 寄存器的地址指针
- xmm 寄存器中的__m128何时?
- 是否可以在 GCC 中使用带有 C++17 的显式寄存器变量?
- 处理器寄存器的大小是多少,有多少个处理器寄存器?
- 如何将 arm64 中的vaddv_u8结果视为氖寄存器