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

标签 c linux gcc optimization 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)))

其中 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 用于检测变量是编译时常量还是未知。我的问题是:当参数是否为编译时常量时,这两个函数之间是否存在性能差异?为什么用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;
}

这正确地输出了句子 j is a...

对于其他情况,只需取消注释将“随机”数字分配给 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我们可以做一个experiment using of 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_bit 和实现者必须相信 constant_test_bit 的常量表达式情况最终优于:

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

对于大多数情况。

至于为什么您的测试用例似乎显示出不同的结论,是因为您的测试用例存在缺陷。我没能解决所有问题。但是如果我们看看 this caseconstant_test_bit 与非常量表达式一起使用,我们可以看到优化器能够将所有工作移到外观之外,并将循环内与 constant_test_bit 相关的工作减少到:

movq    (%rax), %rdi

即使使用较旧的 gcc 版本,但这种情况可能与使用 test_bit 的情况无关。可能会有更具体的情况,这种情况无法进行优化。

关于c - 将参数作为编译时常量或变量传递时函数性能之间的差异,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/30013218/

相关文章:

linux - 如何在Linux shell中循环相同格式的文件

linux - protobuf如何判断一个值是属于可选字段,还是属于另一个对象?

c++ - 批准的避免左值转换警告和错误的方法?

c++ - 列表初始化中元素的评估顺序

c - 尝试将数组写入文件时检测到堆栈粉碎错误

c - 如何获取UTF-8字符的值

linux - 在 linux mint 中运行汇编程序时需要帮助

c++ - 我的 C++ 代码在宏+模板编译时失败,为什么会出现这个错误?

c - 如何在c程序中表达这种数学关系

c - 在c中写一个子串函数