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 == 1clz(x) == 31-然后x == 2^0所以lg(x) == 031 - 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,不变的。这不仅仅是针对特定架构的调整问题,因为一堆额外的指令不会比普通的指令快,而且在较慢的代码中使用bsrstill results
64位输入和输出的情况是even slightly worse:在关键路径中有一个额外的bsr,它似乎什么也不做,因为-mtune=haswell的结果永远不会为负。同样,movsx变量对于单个clz指令也是最优的。
最后,让我们检查一下非windows编译器空间中的大玩家,-march=haswell and bsricc只是做了个糟糕的工作,添加了一些多余的东西,比如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

08-16 02:50