Android 本地代码中的LIKELY和UNLIKELY宏


阅读Android Frameworks中的C++代码时,经常会碰到在条件判断语句中使用了LIKELY和UNLIKELY这两个宏,找到这两个宏的定义如下:

  1. #define LIKELY( exp )       (__builtin_expect( (exp) != 0, true  ))  
  2. #define UNLIKELY( exp )     (__builtin_expect( (exp) != 0, false ))  

long __builtin_expect (long exp, long c) 是GCC的内建函数,解析如下:

你可以使用__builtin_expect给编译器提供分支预测的信息,通常,你因该明确使用这个编译选项(‘-fprofile-arcs’),因为很多程序员在如何预测他们编写的代码实际如何执行方面都很糟糕,使用这个宏可以很方面地让编译器优化分支跳转的代码。

这个函数的返回值就是exp:一个整形表达式,c必须是一个常量,该内建函数从语义上是表明:我们期望exp == c。

所以,如果你不考虑程序执行的效率,加不加LIKELY和UNLIKELY宏,执行的结果是一样的:

  1. if( LIKELY(exp) )   
  2. {   
  3. }   
  4. else  
  5. {   
  6. }  

  1. if(exp)   
  2. {   
  3. }   
  4. else  
  5. {   
  6. }  

执行的结果是一样的。

那为什么还要使用这两个宏定义? 以汽车的速度为例子,如果速度超过200公里/小时表示有异常发生,代码可以这样写:

  1. if(speed >= 200){   
  2.     //异常处理代码   
  3.     .....   
  4.     stop();   
  5. }else{   
  6.     //正常处理代码   
  7.     continue();   
  8. }  

也可以这样写:

  1. if(speed < 200){   
  2.     //正常处理代码   
  3.     continue();   
  4. }else{   
  5.     //异常处理代码   
  6.     .....   
  7.     stop();   
  8. }  

这两个方案执行后都是正确的,但是显然效率是不一样的,因为大多数情况下,汽车的速度不会超过200公里/小时,当采用第一个方案时,大多数情况下,代码执行到这里时CPU都要执行分支跳转的操作,这破坏了CPU的指令执行流水线,对性能的影响是显而易见的。而第二个方案则避免了这一问题,因为大多数时候都是顺序执行的。

对于第一种方案,我们可以加上UNLIKELY宏来让编译器来优化:

  1. if(UNLIKELY(speed >= 200)){   
  2.     //异常处理代码   
  3.     .....   
  4.     stop();   
  5. }else{   
  6.     //正常处理代码   
  7.     continue();   
  8. }  

加上UNLIKELY宏后,相当于告诉编译器:速度大于200是很少出现的。这样编译器在编译代码时,会适当地调整条件判断的方式,让CPU的指令执行顺序尽可能不被打乱,已达到优化性能的效果。

所以,对于第二种方案,我们同样可以加上LIKELY宏:

  1. if(LIKELY(speed < 200)){   
  2.     //正常处理代码   
  3.     continue();   
  4. }else{   
  5.     //异常处理代码   
  6.     .....   
  7.     stop();   
  8. }  

相关内容