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

Xia*_*Zhu 7 c linux optimization gcc linux-kernel

在Linux内核代码中有一个用于测试位的宏(Linux版本2.6.2):

#define test_bit(nr, addr)                      \
        (__builtin_constant_p((nr))             \
         ? constant_test_bit((nr), (addr))      \
         : variable_test_bit((nr), (addr)))
Run Code Online (Sandbox Code Playgroud)

其中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;
}
Run Code Online (Sandbox Code Playgroud)

据我所知,__builtin_constant_p它用于检测变量是编译时常量还是未知.我的问题是:当参数是编译时常量时,这两个函数之间是否存在性能差异?为什么使用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 constant\n");
        return 0;
}
Run Code Online (Sandbox Code Playgroud)

这正确输出句子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
Run Code Online (Sandbox Code Playgroud)

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

j is compile time constant

real    0m0.885s
user    0m0.883s
sys     0m0.000s
Run Code Online (Sandbox Code Playgroud)

变量,constant_test_bit:

real    0m0.485s
user    0m0.477s
sys     0m0.007s
Run Code Online (Sandbox Code Playgroud)

变量,variable_test_bit:

real    0m3.471s
user    0m3.467s
sys     0m0.000s
Run Code Online (Sandbox Code Playgroud)

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

Sha*_*our 5

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

// 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 ;
}
Run Code Online (Sandbox Code Playgroud)

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

shrq    $21, %rax
andl    $1, %eax
Run Code Online (Sandbox Code Playgroud)

而非常量表达式案例最终如下:

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

因此,优化器能够为常量表达式情况生成更好的代码,并且我们可以看到,constant_test_bit与手动汇编的情况相比,非常量情况非常糟糕,variable_test_bit并且实现者必须相信常量表达式constant_test_bit最终会更好比:

btl %edi,8(%rsp)
sbbl %esi,%esi 
Run Code Online (Sandbox Code Playgroud)

对于大多数情况.

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

movq    (%rax), %rdi
Run Code Online (Sandbox Code Playgroud)

即使是旧gcc版本,但这种情况可能与test_bit正在使用的案例无关.可能有更具体的情况,这种优化是不可能的.

  • @XiangyuZhu优化已经大大改进,因为Linux启动后,手优化可能不再像过去那样重要,但要弄清楚需要了解具体的用例.这样做的危险在于,人们可能会盲目地复制这种优化而不对其案例进行基准测试. (2认同)