Eri*_*tch 3 c++ bit-manipulation x86-64 arm64 apple-m1
最初我尝试了 lzcnt 但似乎在 Mac 上不起作用。我正在与使用 Apple M1 CPU(ARM64v8.4)的人一起工作
在这个列出ARM 8的arm文档中,clz支持使用0
CLZ Xd、Xm
计数前导零(64 位):将 Xd 设置为 Xm 最高有效端的二进制零的数量。结果将在 0 到 64 范围内(含 0 和 64)。
我们最初支持的CPU是x86-64,它有_lzcnt_u64
如果值为 0,两条指令似乎都会返回 64。具体来说,ARM 上的“0 到 64包含在内”,英特尔网站也建议这样做(并由我的代码确认)
然而 GCC 说如下
内置函数:int __builtin_clzll (unsigned long long)
与 __builtin_clz 类似,但参数类型为 unsigned long long。
我可以安全地使用 0 还是这个内置函数使用不同的指令?我尝试了 clang,消毒剂停止了程序,并告诉我这是一个令我惊讶的问题。
如果我像这两条指令一样传入 0,那么当我想获得 64 时,我应该如何获得前导零计数
如果 C++20 功能可用,std::countl_zero<T>就可以解决问题,并且编译器开发人员需要以即使输入为零也能高效编译的方式来实现它。(在这种情况下,需要返回您传递的整数类型中的值位数,与 不同__builtin_clzll)
这在理论上很棒,但不幸的是,他们面临着与使用实际内置函数相同的问题。libstdc++ 只是使用x ? __builtin_clzll(x) : 64或多或少,即使有硬件指令可64生成0. (请参阅下面的 C++ 和 asm 代码块。)
因此,实际上,即使在 ARM64 或 x86-64 等机器上,即使在编译时已知可用(或) std::countl_zero<uint64_t>,也始终使用 GCC 编译为多条指令。clang 确实优化了三元。lzcnt-mlzcnt-mbmi
__builtin_clzll63-bsr(x)如果您不使用-march=包含 BMI1 的选项(或者对于某些 AMD,至少是 LZCNT,而不使用 BMI1 的其余部分),将在 x86 上编译。对于输入 0,BSR 不修改其目的地,不进行生产-1或其他操作。 https://www.felixcloutier.com/x86/bsr (这可能是以这种方式定义内置函数的很大一部分动机,因此它可以编译为单个 BSR 或 BSF 指令,而无需条件分支或 x86 长的 cmov在 lzcnt 存在之前。某些用例不需要将其与零一起使用。)
可移植的 GNU C++ 的目标是什么?或者通过特定选项针对几个编译目标完美优化 asm?
您可以尝试x ? __builtin_clzll(x) : 64并希望 GCC 在可用时将其优化到 x86-64 lzcnt。 clang确实做了这个优化,但不幸的是 GCC11 没有。 (神箭)
unsigned clz(unsigned long long x) {
return x ? __builtin_clzll(x) : 64;
}
Run Code Online (Sandbox Code Playgroud)
顺便说一句,libstdc++ 的std::countl_zero编译方式相同,大概是因为它或多或少是这样写的。(可能用一些std::numeric_limits<T>东西而不是硬编码64)。
# x86-64 clang 13 -O3 -march=haswell
clz(unsigned long long):
lzcnt rax, rdi
ret
Run Code Online (Sandbox Code Playgroud)
x86-64 GCC 11.2 -O3 -march=haswell
clz(unsigned long long):
xor edx, edx # break output dependency for lzcnt on Intel pre-SKL
mov eax, 64
lzcnt rdx, rdi
test rdi, rdi
cmovne eax, edx # actually do the ternary
ret
Run Code Online (Sandbox Code Playgroud)
ARM64 的情况相同:clang 将冗余选择操作优化为clz,而 GCC 则没有:
# ARM64 gcc11.2 -O3 -march=cortex-a53
clz(unsigned long long):
cmp x0, 0
clz x0, x0
mov w1, 64
csel w0, w0, w1, ne
ret
Run Code Online (Sandbox Code Playgroud)
#ifdef __BMI__
#include <immintrin.h>
unsigned clz_lzcnt(unsigned long long x) {
return _lzcnt_u64(x);
}
#endif
Run Code Online (Sandbox Code Playgroud)
# x86-64 GCC11 -O3 -march=haswell
clz_lzcnt(unsigned long long):
xor eax, eax
lzcnt rax, rdi
ret
Run Code Online (Sandbox Code Playgroud)
(所以你真的想#ifdef 在另一个函数中使用它,并用 #else 使用三元作为后备。)
| 归档时间: |
|
| 查看次数: |
1984 次 |
| 最近记录: |