除非Haswell指定

时间:2016-11-07 01:17:44

标签: c performance gcc x86 intel

GCC支持__builtin_clz(int x)内置,它计算参数中前导零(连续最重要的零)的数量。

除了 0 之外,这对于有效实现lg(unsigned int x)函数非常有用,该函数取x的基数为2的对数,向下舍入 1 < / SUP>:

/** 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处使用其他默认选项:adds 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 的转变bsrclz的结果。然后执行31 - clz(x)

然而,对于-mtune=haswellcode simplifies完全符合预期的单bsr条指令:

lg(unsigned int):
        bsr     eax, edi
        ret

为什么这种情况对我来说非常不清楚。在Haswell之前,bsr指令已经存在了几十年,并且AFAIK的行为没有改变。这不仅仅是针对特定arch的调整的问题,因为bsr +一堆额外指令不会比普通bsr更快,而且还使用{较慢的代码中{1}} still results

64位输入和输出的情况为even slightly worse:关键路径中有一个额外的-mtune=haswell,因为来自{{{{}}的结果似乎什么都不做1}}永远不会消极。同样,movsx变体最适合单clz条指令。

最后,让我们检查一下非Windows编译器空间中的大玩家icc and clang-march=haswell只是做得不好而且添加了bsr等多余的东西 - wtf?无论icc如何,neg eax; add eax, 31; neg eax; add eax, 31;都能做得很好。

0 例如扫描第一个设置位的位图

1 0的对数是未定的,因此使用0参数调用我们的函数是未定义的行为

2 这里,LSB是第0位,MSB是第31位。

3 回想一下两个补充中的clang

1 个答案:

答案 0 :(得分:7)

这看起来像gcc:https://gcc.gnu.org/bugzilla/show_bug.cgi?id=50168

的已知问题