Chinaunix首页 | 论坛 | 博客
  • 博客访问: 17893
  • 博文数量: 7
  • 博客积分: 1400
  • 博客等级: 上尉
  • 技术积分: 80
  • 用 户 组: 普通用户
  • 注册时间: 2009-12-19 13:01
文章分类

全部博文(7)

文章存档

2010年(4)

2009年(3)

我的朋友
最近访客

分类:

2009-12-20 03:55:07

    今天晚上看书的时候遇到了likely与unlikely这两个东西,书上大概是说这东西一般放到if条件判断中来优化条件分枝,比如说:foo变量在大多情况下都为非0,那么判断的时候可以这样优化

if (likely(foo))
{
    /* do something */
}
else
{
    /* do something else */
}

    反之,如果foo在大多情况下都为0,那么就用unlikely代替likely。

    本来想睡觉了,电脑都关了。刷完牙后又想到了这个东西觉得有必要看一下它到底怎么回事。
    原来这两个东西是宏定义:

#define likely(exp) __builtin_expect((exp),1)
#define unlikely(exp) __builtin_expect((exp),0)

    __builtin_expect是gcc内置的"函数",带有2个参数,第一个参数为一个表达式,第二个参数为一个编译期常量,返回值是表达式的值。这个函数的语义是:你期望的表达式的值是这个常量的值。

    我想知道这东西是怎样优化分支的,用下边一段代码进行测试:

#include <stdio.h>

#define likely(x) __builtin_expect((x),1)
#define unlikely(x) __builtin_expect((x),0)

int main()
{
    int i;
    volatile int n; /*加volatile是为了产生下边两个赋值的汇编指令,不然编译器优化产生一些我们不想要的结果*/
    scanf("%d", &i);
    if (unlikely(i))
        n = 3;
    else
        n = 5;
    return n;
}


    编译的时候选择优化编译

gcc -g -O2 test.c -o test


    得到的汇编代码如下:

(gdb) disassemble main
Dump of assembler code for function main:
0x0000000000400550 <main+0>:    sub $0x18,%rsp
0x0000000000400554 <main+4>:    xor %eax,%eax
0x0000000000400556 <main+6>:    mov $0x40067c,%edi
0x000000000040055b <main+11>:    lea 0xc(%rsp),%rsi
0x0000000000400560 <main+16>:    callq 0x400450 <__isoc99_scanf@plt>
0x0000000000400565 <main+21>:    mov 0xc(%rsp),%eax
0x0000000000400569 <main+25>:    test %eax,%eax
0x000000000040056b <main+27>:    jne 0x40057e <main+46>
0x000000000040056d <main+29>:    movl $0x5,0x8(%rsp)    //n = 5
0x0000000000400575 <main+37>:    mov 0x8(%rsp),%eax
0x0000000000400579 <main+41>:    add $0x18,%rsp
0x000000000040057d <main+45>:    retq
0x000000000040057e <main+46>:    movl $0x3,0x8(%rsp)   //n = 3
0x0000000000400586 <main+54>:    jmp 0x400575 <main+37>
End of assembler dump.


    在代码中我们用unlikely,期望else分支(n=5)会执行。所以在判断了条件后紧接着的就是else分支的代码,而把if分支的代码放到了后边。这样在大多数情况下(也就是期望情况下)程序都会顺序执行不需要跳转,这样就可以提高效率。(cache命中率高)

   将unlikely改为likely后,得到的汇编代码如下:

(gdb) disassemble main
Dump of assembler code for function main:
0x0000000000400550 <main+0>:    sub $0x18,%rsp
0x0000000000400554 <main+4>:    xor %eax,%eax
0x0000000000400556 <main+6>:    mov $0x40067c,%edi
0x000000000040055b <main+11>:    lea 0xc(%rsp),%rsi
0x0000000000400560 <main+16>:    callq 0x400450 <__isoc99_scanf@plt>
0x0000000000400565 <main+21>:    mov 0xc(%rsp),%eax
0x0000000000400569 <main+25>:    test %eax,%eax
0x000000000040056b <main+27>:    je 0x40057e <main+46>
0x000000000040056d <main+29>:    movl $0x3,0x8(%rsp)
0x0000000000400575 <main+37>:    mov 0x8(%rsp),%eax
0x0000000000400579 <main+41>:    add $0x18,%rsp
0x000000000040057d <main+45>:    retq
0x000000000040057e <main+46>:    movl $0x5,0x8(%rsp)
0x0000000000400586 <main+54>:    jmp 0x400575 <main+37>
End of assembler dump.

    这回我们认为大多情况下都会执行if分支,所以if分支的代码紧接着条件判断之后(其实不采取任何优化也是这个效果,因为if分支本来就在else前边),所以程序在大多情况下都会顺序执行不需要跳转。


    __builtin_expect是gcc加入的一些新特性之一,其实在linux内核源代码里很多地方可以见到。我没有真正去看过内核代码,所以到现在才发现
阅读(692) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~