在 32 位数字中查找第一个(最低)设置位的位置
Find position of first (lowest) set bit in 32-bit number
我需要得到一个32位数字中的1位数字,其中只有一个1位(总是)。 C++或asm中最快的方法。
例如
input: 0x00000001, 0x10000000
output: 0, 28
#ifdef __GNUC__
,使用 __builtin_ctz(unsigned)
计算尾随零 (GCC manual)。 GCC、clang 和 ICC 在所有目标 ISA 上都支持它。 (在没有本机指令的 ISA 上,它将调用 GCC 辅助函数。)
Leading vs. Trailing 是按打印顺序书写时,MSB-first,如 8 位二进制 00000010
有 6 个前导零和一个尾随零。 (当转换为 32 位二进制时,将有 24+6 = 30 个前导零。)
对于 64 位整数,使用 __builtin_ctzll(unsigned long long)
。不幸的是,GNU C bitscan 内置函数不采用 fixed-width 类型(尤其是 leading zeros 版本),但是 unsigned
在 GNU C 上始终是 32 位的x86(虽然不适用于 AVR 或 MSP430)。 unsigned long long
在我知道的所有 GNU C 目标上总是 uint64_t
。
在 x86 上,它将编译为 bsf
or tzcnt
depending on tuning + target options. tzcnt
is a single uop with 3 cycle latency on modern Intel, and only 2 uops with 2 cycle latency on AMD (perhaps a bit-reverse to feed an lzcnt uop?) https://agner.org/optimize/ / https://uops.info/。无论哪种方式,它都得到快速硬件的直接支持,并且 比您在纯 C++ 中可以做的任何事情都快得多 。与 x * 1234567
的成本大致相同(在 Intel CPU 上,bsf
/tzcnt
与 imul r, r, imm
的成本相同,在 front-end 微指令中,back-end端口和延迟。)
对于未设置位的输入,内置函数具有未定义的行为,允许它避免任何额外的检查,如果它可能 运行 为 bsf
。
在其他编译器(特别是 MSVC) 中,您可能需要 TZCNT 的内在函数,例如 _mm_tzcnt_32
来自 immintrin.h
。 (Intel intrinsics guide)。或者您可能需要为 non-SIMD 内在函数包含 intrin.h
(MSVC) 或 x86intrin.h
。
与 GCC/clang 不同,MSVC 不会阻止您将内部函数用于您尚未启用的 ISA 扩展,以便编译器自行使用。
MSVC 对于实际 BSF/BSR 也有 _BitScanForward
/ _BitScanReverse
,但是 AMD 保证(英特尔也实现)的 leave-destination-unmodified 行为仍然没有被这些暴露内在函数,尽管它们 pointer-output API.
- VS: unexpected optimization behavior with _BitScanReverse64 intrinsic - pointer-output 假定总是写成:/
- _BitScanForward _BitScanForward64 missing (VS2017) Snappy - 正确 headers
- How to use MSVC intrinsics to get the equivalent of this GCC code?
TZCNT 在没有 BMI1 的 CPU 上将 解码为 BSF,因为它的 machine-code 编码是 rep bsf
。它们为 non-zero 输入给出相同的结果,因此编译器可以并且总是只使用 tzcnt
因为这在 AMD 上要快得多。 (它们在 Intel 上的速度相同,所以没有缺点。在 Skylake 和更高版本上,tzcnt 没有错误的输出依赖性。BSF 这样做是因为它在输入 = 0 时保持输出不变)。
(这种情况对于 bsr
和 lzcnt
不太方便: bsr returns bit-index, lzcnt returns leading-zero计数。因此,为了在 AMD 上获得最佳性能,您需要知道您的代码只会 运行 在支持 BMI1 / TBM 的 CPU 上,以便编译器可以使用 lzcnt
)
请注意,只要设置了 1 个位,从任一方向扫描都会找到相同的位。所以 31 - lzcnt = bsr
在这种情况下与 bsf = tzcnt
相同。如果移植到另一个只有 leading-zero 计数而没有 bit-reverse 指令的 ISA,可能会有用。
相关:
- Why does breaking the "output dependency" of LZCNT matter? 现代编译器通常知道打破 lzcnt/tzcnt/popcnt 的错误依赖。 bsf/bsr 也有一个,我认为 GCC 在这方面也很聪明,但具有讽刺意味的是可能不是。
- How can x86 bsr/bsf have fixed latency, not data dependent? Doesn't it loop over bits like the pseudocode shows? - 伪代码不是硬件实现。
- https://en.wikipedia.org/wiki/Find_first_set 有更多关于跨 ISA 的位扫描功能。包括 POSIX
ffs()
其中 returns 一个基于 1 的索引并且必须做额外的工作来解释输入为 0 的可能性。
编译器确实可以识别 ffs()
并像内置函数一样内联它(就像它们对 memcpy 或 sqrt 所做的那样),但当您实际上想要一个基于 0 的索引。特别难告诉编译器只设置了 1 位。
我需要得到一个32位数字中的1位数字,其中只有一个1位(总是)。 C++或asm中最快的方法。
例如
input: 0x00000001, 0x10000000
output: 0, 28
#ifdef __GNUC__
,使用 __builtin_ctz(unsigned)
计算尾随零 (GCC manual)。 GCC、clang 和 ICC 在所有目标 ISA 上都支持它。 (在没有本机指令的 ISA 上,它将调用 GCC 辅助函数。)
Leading vs. Trailing 是按打印顺序书写时,MSB-first,如 8 位二进制 00000010
有 6 个前导零和一个尾随零。 (当转换为 32 位二进制时,将有 24+6 = 30 个前导零。)
对于 64 位整数,使用 __builtin_ctzll(unsigned long long)
。不幸的是,GNU C bitscan 内置函数不采用 fixed-width 类型(尤其是 leading zeros 版本),但是 unsigned
在 GNU C 上始终是 32 位的x86(虽然不适用于 AVR 或 MSP430)。 unsigned long long
在我知道的所有 GNU C 目标上总是 uint64_t
。
在 x86 上,它将编译为 bsf
or tzcnt
depending on tuning + target options. tzcnt
is a single uop with 3 cycle latency on modern Intel, and only 2 uops with 2 cycle latency on AMD (perhaps a bit-reverse to feed an lzcnt uop?) https://agner.org/optimize/ / https://uops.info/。无论哪种方式,它都得到快速硬件的直接支持,并且 比您在纯 C++ 中可以做的任何事情都快得多 。与 x * 1234567
的成本大致相同(在 Intel CPU 上,bsf
/tzcnt
与 imul r, r, imm
的成本相同,在 front-end 微指令中,back-end端口和延迟。)
对于未设置位的输入,内置函数具有未定义的行为,允许它避免任何额外的检查,如果它可能 运行 为 bsf
。
在其他编译器(特别是 MSVC) 中,您可能需要 TZCNT 的内在函数,例如 _mm_tzcnt_32
来自 immintrin.h
。 (Intel intrinsics guide)。或者您可能需要为 non-SIMD 内在函数包含 intrin.h
(MSVC) 或 x86intrin.h
。
与 GCC/clang 不同,MSVC 不会阻止您将内部函数用于您尚未启用的 ISA 扩展,以便编译器自行使用。
MSVC 对于实际 BSF/BSR 也有 _BitScanForward
/ _BitScanReverse
,但是 AMD 保证(英特尔也实现)的 leave-destination-unmodified 行为仍然没有被这些暴露内在函数,尽管它们 pointer-output API.
- VS: unexpected optimization behavior with _BitScanReverse64 intrinsic - pointer-output 假定总是写成:/
- _BitScanForward _BitScanForward64 missing (VS2017) Snappy - 正确 headers
- How to use MSVC intrinsics to get the equivalent of this GCC code?
TZCNT 在没有 BMI1 的 CPU 上将 解码为 BSF,因为它的 machine-code 编码是 rep bsf
。它们为 non-zero 输入给出相同的结果,因此编译器可以并且总是只使用 tzcnt
因为这在 AMD 上要快得多。 (它们在 Intel 上的速度相同,所以没有缺点。在 Skylake 和更高版本上,tzcnt 没有错误的输出依赖性。BSF 这样做是因为它在输入 = 0 时保持输出不变)。
(这种情况对于 bsr
和 lzcnt
不太方便: bsr returns bit-index, lzcnt returns leading-zero计数。因此,为了在 AMD 上获得最佳性能,您需要知道您的代码只会 运行 在支持 BMI1 / TBM 的 CPU 上,以便编译器可以使用 lzcnt
)
请注意,只要设置了 1 个位,从任一方向扫描都会找到相同的位。所以 31 - lzcnt = bsr
在这种情况下与 bsf = tzcnt
相同。如果移植到另一个只有 leading-zero 计数而没有 bit-reverse 指令的 ISA,可能会有用。
相关:
- Why does breaking the "output dependency" of LZCNT matter? 现代编译器通常知道打破 lzcnt/tzcnt/popcnt 的错误依赖。 bsf/bsr 也有一个,我认为 GCC 在这方面也很聪明,但具有讽刺意味的是可能不是。
- How can x86 bsr/bsf have fixed latency, not data dependent? Doesn't it loop over bits like the pseudocode shows? - 伪代码不是硬件实现。
- https://en.wikipedia.org/wiki/Find_first_set 有更多关于跨 ISA 的位扫描功能。包括 POSIX
ffs()
其中 returns 一个基于 1 的索引并且必须做额外的工作来解释输入为 0 的可能性。
编译器确实可以识别 ffs()
并像内置函数一样内联它(就像它们对 memcpy 或 sqrt 所做的那样),但当您实际上想要一个基于 0 的索引。特别难告诉编译器只设置了 1 位。