C-将参数作为编译时间常数或变量传递时功能性能之间的差异



在Linux内核代码中,有一个用于测试位的宏(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,%1ntsbbl %0,%0"
                :"=r" (oldbit)
                :"m" (ADDR),"Ir" (nr));
        return oldbit;
}

我知道__builtin_constant_p用于检测变量是编译时间常数还是未知。我的问题是:当参数是编译时间常数时,这两个函数之间是否有任何性能差异?为什么在c版本时使用c版本并在不使用时使用汇编版本?

更新:以下主要功能用于测试性能:

常数,调用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 constantn");
        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

变量,constant_test_bit:

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

变量,variable_test_bit:

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

我的每个版本都运行了几次,上述结果是它们的典型值。constant_test_bit函数似乎总是比variable_test_bit函数更快,无论该参数是否为编译时间常数...对于最后两个结果(当j不是常数时),变量版本甚至都比常数慢。一。我在这里错过了什么吗?

使用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

因此,优化器能够为恒定表达情况产生更好的代码,我们可以看到,与variable_test_bit中的手滚动组件相比,constant_test_bit的非恒定案例非常糟糕,并且实现者必须相信恒定的表达式案例对于constant_test_bit,最终比:

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

在大多数情况下。

为什么您的测试案例似乎显示出不同的结论是您的测试案例存在缺陷。我无法解决所有问题。但是,如果我们使用具有非恒定表达式的constant_test_bit查看这种情况,我们可以看到优化器能够将所有工作移动到外观之外,并减少LOOP内部与constant_test_bit相关的工作。

movq    (%rax), %rdi

即使使用了较旧的gcc版本,但这种情况可能与test_bit的情况无关。在某些情况下可能不可能进行此类优化。

最新更新