【问题标题】:difference between the function performance when passing parameter as compile time constant or variable将参数作为编译时常量或变量传递时的功能性能差异
【发布时间】:2015-05-08 02:18:54
【问题描述】:

在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版,没有的时候用汇编版?

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

常量,调用常量_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编译,结果如下:

常数,常数_test_bit:

$ time ./a.out 

j is compile time constant

real    0m0.454s
user    0m0.450s
sys     0m0.000s

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

j is compile time constant

real    0m0.885s
user    0m0.883s
sys     0m0.000s

变量,常量_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 不是常数时)变量版本是甚至比恒定的慢得多。 我在这里遗漏了什么吗?

【问题讨论】:

  • 可能是,但找出答案的唯一方法是测量。
  • 显然有人认为它对性能有影响,否则不会有 2 个版本。有关详细信息,您需要考虑 4 种情况(将常量/非常量传递给任一函数)。你认为每种情况会发生什么?你看过生成的程序集了吗?
  • @deviantfan 我已经添加了性能结果。
  • @MarcGlisse 这就是我的想法。以后可能会花一些时间研究汇编代码。
  • 请注意,您并没有使用这些函数的“大数组中的一位”功能,而汇编版本可能会胜出(或不会)。

标签: c linux gcc optimization linux-kernel


【解决方案1】:

使用godbolt我们可以做一个experiment using of 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 ;
}

我们看到优化器能够将常量表达式的情况优化为以下:

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 

对于大多数情况。

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

movq    (%rax), %rdi

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

【讨论】:

  • 但是为什么要在参数不固定的情况下使用变量版本呢?在这种情况下,由 gcc 生成的常量版本的汇编似乎仍然比变量版本好。
  • @XiangyuZhu 更新了我的答案,你的测试有一些问题,很难把它们全部梳理出来,但很可能这个选择的最初原因是由我最初列出的原因驱动的回答。我希望他们使用相关案例进行基准测试,并确保假设确实适合但很难知道。
  • 是的,也许我的代码用于测试的方式是如此“特殊”,以至于 gcc 能够比variable_test_bit 优化它更多。我查看了最近的内核代码,发现类似的代码仍然存在,所以这一定是有充分理由的(使用不同版本的函数)。但是,我决定暂时搁置它。感谢您的回答。
  • @XiangyuZhu 自 Linux 启动以来优化已经有了很大的改进,手动优化可能不再像以前那样重要,但要弄清楚这一点需要了解具体的用例。这样做的危险在于,人们可能只是盲目地复制此类优化而不对他们的案例进行基准测试。
猜你喜欢
  • 1970-01-01
  • 2011-02-22
  • 2021-03-01
  • 2011-03-30
  • 1970-01-01
  • 2013-02-24
  • 2016-08-16
  • 2018-05-01
相关资源
最近更新 更多