当前位置:   article > 正文

iOS性能优化系列之__builtin_expect分支预测优化_swift 分支预测 编译选项

swift 分支预测 编译选项

前言

最近想整理下AFNetworking中https认证部分的代码逻辑,结果看到个神奇的东西__builtin_expect,竟然不知道这是干嘛的,搜索了一整子,记录一下,可以作为一个优化点

/*
 *  __Require_noErr_Quiet(errorCode, exceptionLabel)
 *
 *  Summary:
 *    If the errorCode expression does not equal 0 (noErr),
 *    goto exceptionLabel.
 *
 *  Parameters:
 *
 *    errorCode:
 *      The expression to compare to 0.
 *
 *    exceptionLabel:
 *      The label.
 */
#ifndef __Require_noErr_Quiet
    #define __Require_noErr_Quiet(errorCode, exceptionLabel)                      \
      do                                                                          \
      {                                                                           \
          if ( __builtin_expect(0 != (errorCode), 0) )                            \
          {                                                                       \
              goto exceptionLabel;                                                \
          }                                                                       \
      } while ( 0 )
#endif
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25

介绍

LikelyUnlikely官方介绍,有Demo

描述

由于大部分程序员在分支预测方面做得很糟糕,所以 GCC 提供了这个内建函数来帮助程序员处理分支预测,优化程序。其第一个参数 exp 为一个整型表达式,这个内建函数的返回值也是这个 exp ,而 c 为一个编译期常量。这个函数的语义是:你期望 exp 表达式的值等于常量 c ,从而 GCC 为你优化程序,将符合这个条件的分支放在合适的地方。一般情况下,你也许会更喜欢使用 gcc 的一个参数 ‘-fprofile-arcs’ 来收集程序运行的关于执行流程和分支走向的实际反馈信息。
因为这个程序只提供了整型表达式,所以如果你要优化其他类型的表达式,可以采用指针的形式。

likely 和 unlikely 是 gcc 扩展的跟处理器相关的宏:

#define  likely(x)        __builtin_expect(!!(x), 1) 
#define  unlikely(x)      __builtin_expect(!!(x), 0)
  • 1
  • 2

现在处理器都是流水线的,有些里面有多个逻辑运算单元,系统可以提前取多条指令进行并行处理,但遇到跳转时,则需要重新取指令,这相对于不用重新去指令就降低了速度
所以就引入了likelyunlikely目的是增加条件分支预测的准确性,cpu 会提前装载后面的指令,遇到条件转移指令时会提前预测并装载某个分 支的指令。unlikely 表示你可以确认该条件是极少发生的,相反 likely 表示该条件多数情况下会发生。编译器会产生相应的代码来优化 cpu 执行效率。

因此,我们编写代码的时候可以根据分支的执行概率优化处理器的取值操作

int x, y; 
if(unlikely(x > 0)) 
    y = 1; 
else 
    y = -1;
  • 1
  • 2
  • 3
  • 4
  • 5

上面的代码中 gcc 编译的指令会预先读取 y = -1 这条指令,这适合 x 的值大于 0 的概率比较小的情况。 如果 x 的值在大部分情况下是大于 0 的,就应该用 likely(x > 0),这样编译出的指令是预先读取 y = 1 这条指令了。这样系统在运行时就会减少重新取指了。

总结

if(likely(value)) 等价于 if(value)
if(unlikely(value)) 也等价于 if(value)
可以理解为加了这个关键字,判断效果和以前一直,只是告诉编译器,哪个是大概率发生的,预装载,提高效率
__builtin_expect() 是 GCC (version >= 2.96)提供给程序员使用的,目的是将“分支转移”的信息提供给编译器,这样编译器可以对代码进行优化,以减少指令跳转带来的性能下降。

__builtin_expect((x),1) 表示 x 的值为真的可能性更大;
__builtin_expect((x),0) 表示 x 的值为假的可能性更大。

也就是说,使用 likely() ,执行 if 后面的语句 的机会更大,使用 unlikely(),执行 else 后面的语句的机会更大。通过这种方式,编译器在编译过程中,会将可能性更大的代码紧跟着起面的代码,从而减少指令跳转带来的性能上的下降。

个人感觉,这东西你先别管加入的__builtin_expect,if还是当年的if,你大爷还是当年的你大爷,但是你大妈已经不再是你大妈了,跟着的第一个参数就是if里面的表达式,第二个参数可以理解为1为真,表达式大概率成立,代表表达式大概率直接进入if语句,如果0为加,表达式为假,大概率进入else语句,编译器会根据参数进行预装载

AFNetworking里面的案例

#ifndef __Require_noErr_Quiet
    #define __Require_noErr_Quiet(errorCode, exceptionLabel)                      \
      do                                                                          \
      {                                                                           \
          if ( __builtin_expect(0 != (errorCode), 0) )                            \
          {                                                                       \
              goto exceptionLabel;                                                \
          }                                                                       \
      } while ( 0 )
#endif
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

知识点1__builtin_expect
再看下外部调用代码

static BOOL AFServerTrustIsValid(SecTrustRef serverTrust) {
    BOOL isValid = NO;
    SecTrustResultType result;
    __Require_noErr_Quiet(SecTrustEvaluate(serverTrust, &result), _out);

    isValid = (result == kSecTrustResultUnspecified || result == kSecTrustResultProceed);

_out:
    return isValid;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

__Require_noErr_Quiet 这个宏的参数
SecTrustEvaluate(serverTrust, &result) 和跳转标识符 _out 第一个参数是系统评估证书的是否可信的函数,去系统根目录查找,然后返回result值,可以看到内部宏代码
__builtin_expect(0 != (errorCode), 0)
第二个参数为0,代表这个表达式为假的概率更大,也就是说errorCode=0更容易成立,不进入直接分支
goto exceptionLabel 而是进入外部else分支
isValid = (result == kSecTrustResultUnspecified || result == kSecTrustResultProceed); 就是运行时会预先吧else的代码紧跟着if判断,大部分情况下就不需要重新取指令,而是是直接从之前预装载的else语句执行,从而通过编译器的预判断加载,提高cpu的执行效率

知识点2 宏里面的do while
宏定义中的do…while(0)用途

这里就没必要展开了,可以看下上面的文章,无非就是避免复杂的宏展开之后出现一系列问题,因此用do while来避免宏展开后的一系列问题

声明:本文内容由网友自发贡献,转载请注明出处:【wpsshop】
推荐阅读
相关标签
  

闽ICP备14008679号