当传递参数作为编译时间常量或variables时,函数性能之间的差异

在Linux内核代码中有一个用于testing的macros(Linux版本2.6.2):

#define test_bit(nr, addr) \ (__builtin_constant_p((nr)) \ ? constant_test_bit((nr), (addr)) \ : variable_test_bit((nr), (addr))) 

其中constant_test_bitvariable_test_bit被定义为:

 static inline int constant_test_bit(int nr, const volatile unsigned long *addr ) { return ((1UL << (nr & 31)) & (addr[nr >> 5])) != 0; } static __inline__ int variable_test_bit(int nr, const volatile unsigned long *addr) { int oldbit; __asm__ __volatile__( "btl %2,%1\n\tsbbl %0,%0" :"=r" (oldbit) :"m" (ADDR),"Ir" (nr)); return oldbit; } 

我明白, __builtin_constant_p用于检测variables是编译时间常量还是未知的。 我的问题是:当这个参数是一个编译时间常量的时候,这两个函数有什么不同吗? 为什么要使用C版本,而不使用汇编版本呢?

更新:以下主要function用于testing性能:

常量,调用constant_test_bit:

 int main(void) { unsigned long i, j = 21; unsigned long cnt = 0; srand(111) //j = rand() % 31; for (i = 1; i < (1 << 30); i++) { j = (j + 1) % 28; if (constant_test_bit(j, &i)) cnt++; } if (__builtin_constant_p(j)) printf("j is a compile time constant\n"); return 0; } 

这正确地输出句子j是一个…

对于其他情况,只需取消为j分配一个“随机”数字的行的注释,并相应地更改函数名称。 当该行被取消注释时,输出将是空的,这是预期的。

我使用gcc test.c -O1进行编译,结果如下:

常数,constant_test_bit:

 $ time ./a.out j is compile time constant real 0m0.454s user 0m0.450s sys 0m0.000s 

常量,variable_test_bit(省略time ./a.out ,以下相同):

 j is compile time constant real 0m0.885s user 0m0.883s sys 0m0.000s 

variables,constant_test_bit:

 real 0m0.485s user 0m0.477s sys 0m0.007s 

variables,variable_test_bit:

 real 0m3.471s user 0m3.467s sys 0m0.000s 

我有每个版本运行几次,以上结果是他们的典型值。 似乎constant_test_bit函数总是比variable_test_bit函数更快,无论参数是否是编译时间常量或不是…对于最后两个结果(当j不是常数时),variables版本甚至比常数慢得多一。 我在这里错过了什么?

Solutions Collecting From Web of "当传递参数作为编译时间常量或variables时,函数性能之间的差异"

使用godbolt我们可以用constant_test_bit做一个实验 ,以下两个测试函数用-O3标志编译gcc

 // Non constant expression test case int func1(unsigned long i, unsigned long j) { int x = constant_test_bit(j, &i) ; return x ; } // constant expression test case int func2(unsigned long i) { int x = constant_test_bit(21, &i) ; return x ; } 

我们看到优化器能够将常量表达式优化为以下内容:

 shrq $21, %rax andl $1, %eax 

而非常量表达式结束如下:

 sarl $5, %eax andl $31, %ecx cltq leaq -8(%rsp,%rax,8), %rax movq (%rax), %rax shrq %cl, %rax andl $1, %eax 

因此,优化器能够为常量表达式生成更好的代码,并且我们可以看到constant_test_bit的非常variable_test_bitvariable_test_bit的手动汇编相比非常不好,并且实现者必须相信constant_test_bit的常量表达式结束比以下更好:

 btl %edi,8(%rsp) sbbl %esi,%esi 

对于大多数情况。

至于为什么你的测试用例似乎显示出不同的结论就是你的测试用例是有缺陷的。 我一直无法解决所有的问题。 但是,如果我们使用constant_test_bit和非常量表达式来查看这种情况 ,我们可以看到优化器能够将所有工作移动到外观之外,并将与循环内的constant_test_bit有关的工作减少到:

 movq (%rax), %rdi 

即使使用较旧的gcc版本,但这种情况可能与test_bit正在使用的情况test_bit 。可能有更多的特定情况下,这种优化是不可能的。