gcc支持__builtin_clz(int x)
内置函数,它计算参数中前导零(连续的最重要零)的数量。
除此之外,这对于有效实现lg(unsigned int x)
函数非常有用,该函数取x
的2个对数,向下舍入1:
/** return the base-2 log of x, where x > 0 */
unsigned lg(unsigned x) {
return 31U - (unsigned)__builtin_clz(x);
}
这以直接的方式工作-特别考虑情况
x == 1
和clz(x) == 31
-然后x == 2^0
所以lg(x) == 0
和31 - 31 == 0
我们得到了正确的结果。更高的x
值也同样起作用。假设内建是有效实现的,那么这比备用的pure C solutions要好得多。
现在碰巧,count前导零操作实际上是x86中
bsr
指令的对偶。返回参数中最重要的1位2的索引。所以如果有10个前导零,第一个1位在参数的第21位。一般来说,我们有31 - clz(x) == bsr(x)
功能,因此bsr
实际上直接实现了我们想要的lg()
功能,而没有多余的31U - ...
部分。事实上,您可以在行之间读取并看到
__builtin_clz
函数是在考虑bsr
的情况下实现的:如果参数为零,则定义为未定义的行为,当然,“前导零”操作完全定义为32(或int
的位大小是什么),参数为零。因此__builtin_clz
的实现当然是基于有效映射到x86上的bsr
指令的思想。但是,看看gcc实际做了什么,在
-O3
中使用其他默认选项:itadds a ton of extra junk:lg(unsigned int):
bsr edi, edi
mov eax, 31
xor edi, 31
sub eax, edi
ret
xor edi,31
行实际上是一个真正重要的4位的not edi
行,这是从neg edi
中逐个关闭的3,它将bsr
的结果转换为clz
。然后执行31 - clz(x)
。但是,使用
-mtune=haswell
时,将code simplifies转换成预期的单个bsr
指令:lg(unsigned int):
bsr eax, edi
ret
我不清楚为什么会这样。在哈斯韦尔之前,
bsr
指令已经存在了几十年,而且行为是,afaik,不变的。这不仅仅是针对特定架构的调整问题,因为一堆额外的指令不会比普通的指令快,而且在较慢的代码中使用bsr
still results。64位输入和输出的情况是even slightly worse:在关键路径中有一个额外的
bsr
,它似乎什么也不做,因为-mtune=haswell
的结果永远不会为负。同样,movsx
变量对于单个clz
指令也是最优的。最后,让我们检查一下非windows编译器空间中的大玩家,
-march=haswell
and bsr
。icc
只是做了个糟糕的工作,添加了一些多余的东西,比如clang
-wtf?icc
做得很好,不管neg eax; add eax, 31; neg eax; add eax, 31;
。0,例如扫描位图以获取第一个设置位。
1 0的对数是未定义的,因此用0参数调用函数是未定义的行为。
2这里,lsb是第0位,msb是第31位。
3.回想一下,两个字母中的
clang
是互补的。 最佳答案
这似乎是gcc的已知问题:https://gcc.gnu.org/bugzilla/show_bug.cgi?id=50168