CPU 分支预测

  去年在安宁庄的时候, 有个同事阐述了一个观点:php中的if else  在执行时考虑到效率的原因,不会按我们的代码的顺序一条一条去试,而是随机找出一个分支,执行,如果不对,再随机找到一个分支

  当时由于种种原因,也没过多去想这个问题,最近查了下资料,发现里面的学问还挺大的

  php解释器是由c编写的,是个经编译生成的二进制文件, 我们编写的PHP代码相当于这个C程序的参数,只不过这个参数是个一个的文件, 这个C程序要解析这个php文件,产生相应的opcode,再去执行opcode对应的函数,每一部操作都是由C函数来实现

查询opcode含义的利器: http://www.laruence.com/2008/11/20/640.html#ZEND_JMP_.28Opcode_42.29

<?php
if($a == 1){
    echo "a is 1";
}else if($a == 2){
    echo "a is 2";
}else{
    echo "a is x";
}

对于上面的php代码来说,最终执行的opcode是

-------------------------------------------------------------------------------------
   2     0  E >   IS_EQUAL                                         ~0      !0, 1
         1      > JMPZ                                                     ~0, ->4
   3     2    >   ECHO                                                     ‘a+is+1‘
   4     3      > JMP                                                      ->9
         4    >   IS_EQUAL                                         ~1      !0, 2
         5      > JMPZ                                                     ~1, ->8
   5     6    >   ECHO                                                     ‘a+is+2‘
   6     7      > JMP                                                      ->9
   7     8    >   ECHO                                                     ‘a+is+x‘
   9     9    > > RETURN                                                   1

可以看到在执行php时,  是一条一条去执行的

1.先判断  $a  是否 等于 1

2.如果不等于1,为false, 就JMPZ 到第4条命令,去比较  $a 是否 等于2

如果等于1, echo "a is 1"; 然后 无条件跳转 JMP 第9行 return 了

3.如果 $a 不等于2 ,即为false, 就JMPZ 到第8条命令, echo "a is x"

如果  $a 等于2,直接echo "a is 2", 然后执行 JMP 第9行 return 了

所以,php编写的程序,对C函数来说,还是要一步的一步去执行的

  如果这个文件被执行100次,有90次 $a=3, 那么解释器每次都要判断 $a 是否等于1和  $a 是否等于2, 尽管第三个分支是满足条件的,如果是C编写的程序, CPU会针对某种策略挑选一个分支来执行, 对应上面的分支来说,CPU会直接取出第三个分支的指令,然后执行。

从486开始,CPU开始具备流水线这个特性,指令流水线由5,6个不同功能的工作单元组成,将一个x86指令也拆分成5,6个步骤,分别送往不同的工作单元,来达到同时执行多个指令的目的,现在的CPU支持30级的流水线,也就意味着流水线上有30个工作单元,对应的X86指令也拆分成30个步骤。

注:CPU执行的是二进制数据,代码经过汇编编译后,生成一条条二进制指令

例如 int a=1; 对应的汇编是mov $1, %eax; 对应的机器码可能是00011100011

在执行文件时,根据局部性原理,想关的指令都要加载到CPU缓存中,

一般一条指令的完成 分四个步骤:

1.取指令

2.翻译指令 (看是赋值,还是计算,从内存什么地方取数据)

3.执行指令

4.写指令结果 (要么写回内存,要么写到寄存器)

      取指令  翻译指令  执行指令  写指令结果

命令1      命令1

命令2

取指令单元取出指令1后,翻译指令单元开始 翻译指令1时,取指令单元可 取出指令2了

如果CPU不这么做,等到指令1完成上面四个步骤后,指令2才开始进行,那效率太低了

流水化中的单元分的更详细, 更多的指令可以并行处理,但速度不见得快,因为有分支的出现,如果没有命中第一个分支,后面的指令将作废, 需要清空后面所有的指令, 然后中载命中地址的指令,再运行

在有5个分支的情况下,若采取随机挑选一个分支 执行的话,每次赌该分支命中的概率只有五分之一, 于是CPU分支预测功能就出现了。

分支预测分静态和动态

静态分支预测:由编译器决定哪个分支可能被CPU命中,一般是第一个分支,即 if 后面的逻辑,而不是后面else的逻辑

动态分支预测:在CPU硬件中开辟一块缓存,专门记录每个分支最近几次的命中情况,然后做出预测,显然这种方法能及时调整策略,有更好的远詹性,但CPU压力会大些,不过还好。

  

  分支地址只有在流水线指令执行阶段才能计算出来,为了避免等待,需要在译码阶段进行预测

Two-Level分支预测方法使用了两种数据结构,一种是BHR(Branch History Register);而另一种是PHT(Pattern History Table)。其中BHR由k位组成(可理解为记录K次某个分支的执行结果),用来记录每一条转移指令的历史状态,而PHT表含有2k个Entry组成,而每一个Entry由两位Saturating Counter组成。BHR和PHT的关系如图3?10所示。

假设分支预测单元在使用Two-Level分支预测方法时,设置了一个PBHT表(Per-address Branch History Table)存放不同指令所对应的BHR。在PBHT表中所有BHR的初始值为全1,而在PHT表中所有Entry的初始值值为0b11。BHR在PBHT表中的使用方法与替换机制与Cache类似。

当分支预测单元分析预测转移指令B的执行时,将首先从PBHT中获得与转移指令B对应的BHR,此时BHR为全1,因此CPU将从PHT的第11…11个Entry中获得预测结果0b11,即Strongly Taken。转移指令B执行完毕后,将实际执行结果Rc更新到BHR寄存器中,并同时更新PHT中对应的Entry。

当CPU再次预测转移指令B的执行时,仍将根据BHR索引PHT表,并从对应Entry中获得预测结果。而当指令B再次执行完毕后,将继续更新BHR和PHT表中对应的Entry。当转移指令的执行结果具有某种规律(Pattern)时,使用这种方法可以有效提高预测精度。如果转移指令B的实际执行结果为001001001….001,而且k等于4时,CPU将以0010-0100-1001这样的循环访问BHR,因此CPU将分别从PHT表中的第0010、0100和1001个Entry中获得准确的预测结果。

由以上描述可以发现,Two-Level分支预测法具有学习功能,并可以根据转移指令的历史记录产生的模式,在PHT表中查找预测结果。该算法由T.Y. Yeh and Y.N. Patt在1991年提出,并在高性能处理器中得到了大规模应用。

Two-Level分支预测法具有许多变种。目前x86处理器主要使用“Local Branch Prediction”和“Global Branch Prediction”两种算法。

在“Local Branch Prediction”算法中,每一个BHR使用不同的PHT表,Pentium II和Pentium III处理器使用这种算法。该算法的主要问题是当PBHT表的Entry数目增加时,PHT表将以指数速度增长,而且不能利用其它转移指令的历史信息进行分支预测。而在“Global Branch Prediction”算法中,所有BHR共享PHT表,Pentium M、Pentium Core和Core 2处理器使用这种算法。

在高性能处理器中,分支预测单元对一些特殊的分支指令如“Loop”和“Indirect跳转指令”设置了“Loop Prediction”和“Indirect Prediction”部件优化这两种分支指令的预测。此外分支预测单元,还设置了RSB(Return Stack Buffer),当CPU调用一个函数时,RSB将记录该函数的返回地址,当函数返回时,将从RSB中获得返回地址,而不必从堆栈中获得返回地址,从而提高了函数返回的效率。

目前在高性能处理器中,动态分支预测的主要实现机制是CPU通过学习以往历史信息,并进行预测,因而Neural branch predictors机制被引入,并取得了较为理想的效果,本节对这种分支预测技术不做进一步说明。目前指令的动态分支预测技术较为成熟,在高性能计算机中,分支预测的成功概率在95%~98%之间,而且很难进一步提高。

参考:http://blog.sina.com.cn/s/blog_6472c4cc0100qxd2.html

http://tonysuo.blogspot.hk/2013/12/computer-architecture-5.html

http://blog.hesey.net/2013/03/branch-prediction-in-pipeline.html

http://wenku.baidu.com/view/48833667ddccda38376bafa2.html

http://blog.sina.com.cn/s/blog_5a82024e0100e5lm.html

//大话处理器

http://blog.csdn.net/muxiqingyang/article/details/6677425

http://cyukang.com/2012/07/11/branch_prediction.html

http://blog.csdn.net/wahaha_nescafe/article/details/8500094

https://www.zhihu.com/question/23973128

http://blog.sina.com.cn/s/blog_6556314c0100hamf.html

http://blog.sina.com.cn/s/blog_6556314c0100hamt.html

http://blog.sina.com.cn/s/blog_6556314c0100hamj.html

http://blog.sina.com.cn/s/blog_6556314c0100hamh.html

https://www.zhihu.com/question/23973128

时间: 2024-10-31 11:12:13

CPU 分支预测的相关文章

理解CPU分支预测,提高代码效率

摘要: 技术传播的价值,不仅仅体现在通过商业化产品和开源项目来缩短我们构建应用的路径,加速业务的上线速率,也会体现在优秀程序员在工作效率提升.产品性能优化和用户体验改善等小技巧方面的分享,以提高我们的工作能力. 技术传播的价值,不仅仅体现在通过商业化产品和开源项目来缩短我们构建应用的路径,加速业务的上线速率,也会体现在优秀程序员在工作效率提升.产品性能优化和用户体验改善等小技巧方面的分享,以提高我们的工作能力. 从本期开始,我们将邀请来自阿里巴巴各个技术团队的程序员,涵盖中间件.前端.移动开发.

优化技巧:提前if判断帮助CPU分支预测

摘要: 在stackoverflow上有一个非常有名的问题:为什么处理有序数组要比非有序数组快?,可见分支预测对代码运行效率有非常大的影响.要提高代码执行效率,一个重要的原则就是尽量避免CPU把流水线清空,那么提高分支预测的成功率就非常重要. 分支预测 在stackoverflow上有一个非常有名的问题:为什么处理有序数组要比非有序数组快?,可见分支预测对代码运行效率有非常大的影响. 现代CPU都支持分支预测(branch prediction)和指令流水线(instruction pipeli

【CPU微架构设计】利用Verilog设计基于饱和计数器和BTB的分支预测器

在基于流水线(pipeline)的微处理器中,分支预测单元(Branch Predictor Unit)是一个重要的功能部件,它负责收集和分析分支/跳转指令的参数和执行结果,当处理新的分支/跳转指令时,BPU将根据已有的统计结果和当前分支跳转指令的参数,预测其执行结果,为流水线取指提供决策依据,进而提高流水线效率. 下面讨论提出分支预测机制的主要原因和实际意义: 在流水线处理分支跳转指令时,目标地址往往需要推迟到指令的执行阶段才能运算得出,在此之前处理器无法及时得知下一条指令的取指地址,因此无法

浅谈分支预测、流水线与条件转移(转载)

一 一个问题 原文链接:http://www.cnblogs.com/yangecnu/p/4196026.html#undefined 在StackOverflow上有这么一个问题 Why is processing a sorted array faster than an unsorted array? .例子中,对一个数组进行条件求和,在排序前和排序后,性能有很大的差别.原始的例子是C++和Java的,这里将其换成了C# : static void Main(string[] args)

分支预测技术

分支预测(Branch Prediction): 从P5时代开始的一种先进的,解决处理分支指令(if-then-else)导致流水线失败的数据处理方法,由CPU来判断程序分支的进行方向,能够加快运算速度. 当 包含流水线技术的处理器处理分支指令时就会遇到一个问题,根据判定条件的真/假的不同,有可能会产生转跳,而这会打断流水线中指令的处理,因为处理器无法 确定该指令的下一条指令,直到分支执行完毕.流水线越长,处理器等待的时间便越长,因为它必须等待分支指令处理完毕,才能确定下一条进入流水线的指令.

您可能是分支预测的受害者!

背景 现有一个长度N=1000000数组 a[N],每个元素的取值范围为0-255.要求将小于128的元素全部设置为0,大于等于128的元素设置为1 我们很容易写出这样的循环遍历代码 for (i = 0; i < N; i++) { if (a[i] < 128) { a[i] = 0; } else { a[i] = 1; } } 思考一个问题,数组a无序和有序,会对这段代码的执行速度造成影响吗?乍看之下似乎不会,但实际执行时间可能相差3-4倍. 为什么同样是遍历数组,判断元素.遍历有序数

体系结构复习2——指令级并行(分支预测和VLIW)

第五章内容较多,接体系结构复习1 5.4 基于硬件推测的指令级并行 动态分支预测是在程序运行时,根据转移的历史信息等动态确定预测分支方向,主要方法有: 基于BPB(Branch Prediction Buffer)和BHT(Branch History Table)的方法 高性能指令发送(High Performance Instruction Delivery) 5.4.1 基于BPB和BHT的方法 (1)1-bit BHT 分支指令PC的低位索引1位记录上一次转移是否成功(不是预测是否正确)

__builtin_expect — 分支预测优化

1.引言 在很多源码如Linux内核.Glib等,我们都能看到likely()和unlikely()这两个宏,通常这两个宏定义是下面这样的形式. #define likely(x) __builtin_expect(!!(x), 1) #define unlikely(x) __builtin_expect(!!(x), 0) 可以看出这2个宏都是使用函数 __builtin_expect()实现的, __builtin_expect()函数是GCC的一个内建函数(build-in functi

分支预测(branch prediction)

记录一个在StackOverflow上看到一个十分有趣的问题:问题. 高票答案的优化方法: 首先找到罪魁祸首: if (data[c] >= 128) sum += data[c]; 优化方案使用位操作: int t = (data[c] - 128) >> 31; sum += ~t & data[c]; 正数右移31一定为0,负数右移31一定为-1.再取反进行求&(按位与),0与任何数的&为0,-1与任何数的&为数本身.这样就巧妙的避开分支预测了,可以