简单的测试,
unsigned f(unsigned long long x) {
return __builtin_popcountll(x);
}
当使用 clang --target=arm-none-linux-eabi -mfpu=neon -mfloat-abi=softfp -mcpu=cortex-a15 -Os
编译时,⁎ 导致编译器发出实现经典 popcount 所需的大量指令对x
中的低位和高位字进行并行处理,然后将结果相加。
在我看来,通过浏览架构手册,NEON 代码类似于生成的代码
#include <arm_neon.h>
unsigned f(unsigned long long x) {
uint8x8_t v = vcnt_u8(vcreate_u8(x));
return vget_lane_u64(vpaddl_u32(vpaddl_u16(vpaddl_u8(v))), 0);
}
至少在尺寸方面应该是有益的,即使不一定是性能改进。
为什么 Clang† 不这样做?我只是给了它错误的选择吗? ARM 到 NEON 到 ARM 的转换是否非常缓慢,即使在 A15 上也是如此,以至于不值得吗? (这就是 a comment on a related question 似乎暗示的,但非常简短。)鉴于几乎所有现代移动设备都使用 AArch64,AArch32 的 Clang 代码生成是否缺乏关注和关注? (这似乎有些牵强,但众所周知,例如 GCC 偶尔会在 PowerPC 或 MIPS 等非主流架构上出现错误的代码生成。)
<支持> ⁎ Clang 选项可能是错误的或多余的,请根据需要进行调整。† 在我的实验中,GCC 似乎也没有这样做,只是发出对
__popcountdi2
的调用,但这表明我可能只是调用错误。
最佳答案
Are the ARM-to-NEON-to-ARM transitions so spectacularly slow, even on the A15, that it wouldn’t be worth it?
嗯,你问的很对。
很快,是的,是的。它很慢,在大多数情况下,在 NEON 和 ARM CPU 之间移动数据,反之亦然,这是一个很大的性能损失,超过了使用“快速”NEON 指令带来的性能提升。
详细来说,NEON 是基于 ARMv7 的芯片中的可选协处理器。
ARM CPU 和 NEON 并行工作,我可以说彼此“独立”。
CPU 和 NEON 协处理器之间的交互通过 FIFO 组织。 CPU 将 neon 指令放入 FIFO 中,NEON 协处理器获取并执行它。
当 CPU 和 NEON 需要彼此同步时,就会出现延迟。 Sync 正在访问相同的内存区域或在寄存器之间传输数据。
所以使用vcnt
的整个过程是这样的:
- ARM CPU 将
vcnt
放入 NEON FIFO - 将数据从 CPU 寄存器移动到 NEON 寄存器
- NEON 从 FIFO 中获取
vcnt
- NEON 执行
vcnt
- 将数据从 NEON 寄存器移动到 CPU 寄存器
CPU 一直在等待,而 NEON 正在执行它的工作。
由于 NEON 流水线,延迟可能高达 20 个周期(如果我没记错的话)。
注意:“最多 20 个周期”是任意的,因为如果 ARM CPU 有其他不依赖于 NEON 计算结果的指令,CPU 可以执行它们。
结论:根据经验,这是不值得的,除非您手动优化代码以减少/消除同步延迟。
PS:ARMv7 也是如此。 ARMv8 将 NEON 扩展作为核心的一部分,因此它不相关。
关于arm - 为什么 Clang 不对 AArch32 上的 __builtin_popcountll 使用 vcnt?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/70008561/