关键字

  • 圈复杂度
  • CPU分支预测机制
  • 指令
  • 吞吐量
  • IPS-每秒指令
  • GIPS-每秒十亿指令
  • 延迟-皮秒
  • 分支预测

if-else走天下

圈复杂度

void sort(int *A)
{
int i = 0;
int n = 5;
int j = 0;
while (i < (n - 1))
{    j = i + 1;   while (j < n) {      if (A[i] < A[j])       {        swap(A[i], A[j]);      }    }    i = i + 1;  }  if (A) {   // 2018年    if (~)  }  if (B) {     // 2019年    if (~)  }  if (C) {     // 2020年    if (~)  }  if (D) { // 2021年    if (~)  }
}

圈复杂度详解:圈复杂度,以上伪代码,圈复杂度较高,计算公式V(G) = P + 1,P为判断节点数量,上述圈复杂度为11,各企业有自己的治理阈值。

懵懂少年

单从伪代码逻辑看,陈年老代码,很香醇。但你发现需要新增功能时,傻了眼,只能在最后面添加更多的if-else条件。这是最简单、可靠的扩展方式。

一开始看到类似的代码,心里总是有一股冲动,重构重构重构,重要事情说三遍。上设计模式,把他好好重构下,那是你没有碰到质量事故,小公司随便你玩,大公司你搞几次质量事故估计领导就要和你谈话了。

重构这类事情不是一个程序员去左右的,需要企业从上至下的推动才能最终落地。想之前在阿里接手一个七八年的老项目-高德快速响应平台-rus.amap.com,跟领导再三强调,需要花一个季度去重构,领导最终同意了重构,咱就哼痴哼痴的改起来改框架、改逻辑。最终还是搞成了。

与之相反的一个案例是,在改造一个功能模块时,没考虑全面,把另一个功能整废了。揪心,天天手动给用户处理问题。

最终得到的结论是,if-else陈年老代码,在摸不透情况下,不想背锅,不要改。

CPU分支预测

分支预测来历


200年前没有无线电,火车变轨需要人工干预,火车要想快速通过就需要拉闸的那个人准确判断对应列车要走那条轨道。

  • 事先已知道对应列车走那条,火车就不用提前减速停车,等待变轨。高效。
  • 事先不知道,火车减速,停车,等待变轨。耗时。

这是一个事例,类比下计算机程序的分支条件执行,计算机要想高效执行一段程序,就需要一种预判未来的机制来保证。因此就需要预判。

洗车流水线机制


图示为一种标准化洗车流程,并不使用所有场景,仅用来类比,有错误不吝指出。

关于流水线(pipeline),这里举一个生活中的例子,比如在洗车时,当前面一辆车清洗完成进入擦洗阶段后,下一辆车就可以进入喷水阶段了,这就是一个典型的流水线场景(如图所示),它不是说非要前面一辆车把清洗、擦洗全部完成后,下一辆车才能开始。一条洗车流水线可同时洗四辆车。洗车吞吐为4量车。

洗车中流水线洗车中流水线从这里也可以看出,流水线机制一个重要的特性就是提高了系统的吞吐量,也就是单位时间内服务的总数,不过它会有一个轻微的延迟,对于上面的例子就是,一辆汽车在洗完之后需要开到擦洗的地方擦洗。在 CPU 的设计中,也有类似流水线化的机制,这个汽车就是指令1,每个阶段完成任务的一部分。

CPU 中计算流水线

github上有一个有名的问题《Why is processing a sorted array faster than processing an unsorted array?》,就是关于分支预测的问答,第一个答案认同率最高,讲得很有道理。

下面举一个示例,这里将系统执行分为三个阶段(A、B 和 C),如下图所示,每个阶段需要 100ps(picosecond,皮秒,也就是微微秒,即 10−1210^{-12}10−12),中间加载寄存器(也可以叫做流水线寄存器,pipeline register)需要 20ps。对于图 b,时间从左往右流动,对于指令 I1,三个方框分别代表三个阶段(图片来自 《深入理解计算机系统 第三版》 中插图)。

这样,每条指令都会按照三步经过这个系统,从头到尾需要三个完整的时钟周期,如上图所示,只要 I1 从 A 进入 B,就可以让 I2 进入 A 阶段了,以此类推。在稳定状态下,三个阶段都应该是活动的,每个时钟周期(clock cycle),一条指令离开系统,一条新的指令进入。在这个系统中,时钟周期设为 100+20=120ps,得到的吞吐量大约为 8.33GIPS2,但是因为处理一条指令需要 3 个时钟周期,所以这条流水线的延迟就是 3*120=360ps,它相当于 一阶段 的系统,吞吐量提高了 2.67 倍,代价是增加了一些硬件以及延迟的增加(寄存器变多带来的延迟)。

吞吐量计算公式 T = 1/clock cycle

流水线的局限

在上面的三阶段系统中,它是一个比较理想的情况,在这个系统中,我们可以将计算分成三个独立的阶段,每个阶段需要的时间是原来逻辑需要时间的三分之一,但是在实际生产中,会出现一些其他的因素,降低流水线的效率。

  • 情况 1:阶段不一致的划分

在前面的例子划分的阶段中,每个阶段执行都是 100ps,但是实际中并不一定是这样的,假如 A 阶段是 50ps,B 阶段是 150ps,C 阶段是 100ps,在这种情况下,系统必须将时钟周期设置为 170ps(由最慢的来决定),这样的话,其吞吐量就变成了 5.88GIPS,由于时钟减慢,也导致了延迟增加到了 510ps。

因此,在 CPU 硬件设计时,将系统计算设计分为一组具有相同延迟的阶段将是一个严峻的挑战。

  • 情况 2:流水线过深,收益反而下降

如果流水线过深,中间使用到的寄存器将会变多,寄存器使用带来的延迟在指令运行总延迟中的比重将会增大。一方面,在设计时,为了提高时钟频率,现代处理器会采用很深的流水线,另一方面,由于流水线过深,指令运行延迟会变长。所以,在实际设计时,电路设计师如何设计流水线寄存器,使其延迟尽可能减少,是高速微处理器面临重大挑战之一。

现代CPU流水线都会比较深,正常一个三阶段的主频8GHZ,但实际CPU主频都没有这么高一般2.6GHZ,粗略估计时钟周期为380ps,不一定准确,仅供参考。

为什么有的CPU会标注出主频范围2.6~8GHZ,其中的8GHZ如此高频怀疑是CPU内部有动态流水线配置,自动识别指令匹配流水线。需要计算机专业老师帮你指正。

什么是分支预测

前面了解到 CPU 为了提高吞吐量采用了流水线机制,比如下图中的 4 级流水线(CPU Instruction pipelining3):

上图中的 CPU pipeline 有四个执行阶段:

  • 读取指令(Fetch);
  • 指令解码(Decode);
  • 运行指令(Execute);
  • 回写(Write-back)。

假设有三条指令1,2,3,4,在上面这个四级流水线构架下(每个阶段都会花费一个时钟周期),为了便于理解,特地画了个pipeline 执行流程如下图所示:

图中红框表示一条流水线处理指令容量。

我们知道:如果没有流水线机制,一条指令大概会花费 4 个时钟周期,而如果采用流水线机制,当第一条指令完成Fetch后,第二条指令就可以进行Fetch了,极大提高了指令的执行效率。假设流水线深度为4,一条流水线将能同时处理4条指令。比一条流水线处理1条指令提效了4倍。

上面是我们的期待的理想情况,而在现实环境中,如果遇到的指令是 条件跳转指令,只要当前面的指令运行到执行阶段,才能知道要选择的分支,显然这种 停顿对于 CPU 的 pipeline 机制是非常不友好的。而 分支预测技术 正是为了解决上述问题而诞生的,CPU 会根据分支预测的结果,选择下一条指令进入流水线。待跳转指令执行完成,如果预测正确,则流水线继续执行,不会受到跳转指令的影响。如果分支预测失败,那么便需要清空流水线,重新加载正确的分支(实际上目前市面上所有处理器都采用了类似的技术)。

程序中需要进行条件跳转的场景其实很多,多态、条件分支等。

一句话概括,先给出一个预测结果,让流水线直接执行,执行对了则结束;执行错了,则退回去重新执行,直到对为止。大胆干,错了重来。

分支预测技术

这里看下常见的分支预测技术,主要有:静态分支预测、动态分支预测和协同分支预测 三种,有兴趣的可以看下下面的几篇文章:

关于这三种技术,这里就不再展开了,简单总结一下,细节可以百度。

  • 静态分支预测(傻傻的随机):实现起来很简单、成本低,而且在生产中,这种预测正确率的波动范围很大;
  • 动态分支预测(记录下概率):根据指令的不同及历史信息(存储在一张分支历史表中 —— Branch History Table)作出相应的预测,常见的有 1-bit/n-bit 动态预测;
  • 协同分支预测(复杂技术,我也不懂):利用代码中分支跳转指令之间的关联关系,提高分支预测的准确率。

Java 中的虚函数调用

Java 本身没有虚函数的概念,它在 C++ 中是最常见的。在 C++ 中,虚函数通过 virtual 关键字定义,实现在类的继承当中,编译器通过判断对象的类型,在调用函数时,执行对应的函数。Java 中并没有显式去定义虚函数的概念,Java 中实际上每个函数都默认是一个虚函数(声明 final 关键字的函数除外),比如下面示例中 eat() 方法。

public class Animal {public void eat() { System.out.println("loongshawn eat like a generic Animal."); }
}public class Dog extends Animal {public void eat() { System.out.println("loongshawn eat like a dog!"); }
}public class Cat extends Animal {public void eat() { System.out.println("loongshawn eat like a cat!"); }
}public static void main(String[] args) {List<Animal> animals = new LinkedList<Animal>();animals.add(new Animal());animals.add(new Dog());animals.add(new Cat());for (Animal currentAnimal : animals) {currentAnimal.eat();}
}

虚函数存在的意义就是为了实现多态,Java 通过动态绑定,不仅实现了虚函数的功能,也使得代码逻辑更为简洁。

小结

简单回顾了下知识点,内容很多,讲得粗糙,权当科普。相信大家已经对 CPU 的流水线机制及 CPU 的分支预测技术有了一定的了解。

回到最上面那段代码上,逻辑中有11个分支。如果代码里充满着各种不可预知的条件跳转指令,将会极大影响 CPU 的执行效率,在这个逻辑中,最大的代价是可能导致错误的 CPU 分支预测,错误的分支预测会导致需要 11个周期的系统开销。

文中讲得不对的,请指正,谢谢。


  1. 指令-https://zh.wikipedia.org/wiki/%E6%AF%8F%E7%A7%92%E6%8C%87%E4%BB%A4 ↩︎

  2. GIPS 每秒十亿指令 ↩︎

  3. CPU流水线-CPU Instruction pipelining ↩︎

if-else走天下,让CPU分支预测技术浮出水面相关推荐

  1. 阿里程序员工作小技巧:理解CPU分支预测,提高代码效率

    技术传播的价值,不仅仅体现在通过商业化产品和开源项目来缩短我们构建应用的路径,加速业务的上线速率,体现也会在优秀程序员在工作效率提升,产品性能优化和用户体验改善等小技巧方面的分享,以提高我们的工作能力 ...

  2. 阿里程序员工作小技巧 | 理解CPU分支预测,提高代码效率

    技术传播的价值,不仅仅体现在通过商业化产品和开源项目来缩短我们构建应用的路径,加速业务的上线速率,也会体现在优秀程序员在工作效率提升.产品性能优化和用户体验改善等小技巧方面的分享,以提高我们的工作能力 ...

  3. cpu 分支预测对性能的影响

    cpu 分支预测对性能的影响 现在的 cpu 一般都支持分支预测功能.维基百科中有以下描述: 在计算机体系结构中,分支预测器(英语:Branch predictor)是一种数字电路,在分支指令执行结束 ...

  4. 用三元操作符替代if-else以降低CPU分支预测惩罚实现Unity内函数13倍提速

    测试对象 1,C# (Unity脚本) 2,C# DLL(mcs build的动态链接库再导入Unity) 3,C Native Code(LLVM编译后导入Unity) 被测试函数源码 两个随机数数 ...

  5. 英特尔cpu发布时间表_英特尔10nm芯片开始大规模出货,先进制程时间表浮出水面...

    多年延期之后,英特尔终于宣布其 10nm 芯片产品开始大量出货. 近日,英特尔公布了公司 2019 年 Q3 财报.在财报会议中,英特尔透露了这一消息.具体而言,英特尔已有晶圆厂开始大批量生产 10n ...

  6. #C++# #likely# #unlikely#减少CPU流水线分支预测错误带来的性能损失

    目录 流水线技术 分支预测 什么是likely和unlikely likely/unlikely的原理 likely/unlikely的适用条件 C++20中的likely/unlikely 流水线技 ...

  7. 超标量处理器设计——第四章_分支预测

    超标量处理器设计--第四章_分支预测 参考<超标量处理器>姚永斌著 4.1 简述 分支预测主要与预测两个内容, 一个是分支方向, 还有一个是跳转的目标地址 首先需要识别出取出的指令是否是分 ...

  8. 一步步编写操作系统 30 cpu的分支预测简介

    人在道路的分岔口时要预测哪条路能够到达目的地,面对众多选择时,计算机也一样要抉择,毕竟计算机的运行方式是以人的思路来设计的,计算机中的抉择其实就是人在抉择. cpu中的指令是在流水线上执行.分支预测, ...

  9. 一步步编写操作系统 31 cpu的分支预测 下

    让我们说说预测的算法吧. 对于无条件跳转,没啥可犹豫的,直接跳过去就是了.所谓的预测是针对有条件跳转来说的,因为不知道条件成不成立.最简单的统计是根据上一次跳转的结果来预测本次,如果上一次跳转啦,这一 ...

最新文章

  1. Python实现图片识别加翻译,高薪必备技能
  2. dede日期时间标签调用大全
  3. 推荐CVer的总结 | 性能最强的One-stage目标检测算法
  4. java 新区 老区_优学院《土地资源学》答案在线查题2020高校邦《Java核心开发技术【实境编程】》章测试答案...
  5. MATLAB错误:‘conv2’
  6. codechef Polo the Penguin and the Tree
  7. 朋友圈的尖子生--自序有感
  8. 企业要想迅速壮大,不仅需要大量的人才
  9. GRIB格式转换心得(转自博客:http://windforestwing.blog.163.com/blog/static/19545412007103084743804/)...
  10. MyBatis常见问题和解决方法
  11. 差分进化算法DE优化BPNN
  12. 获取文件夹下所有tif图片,并将16位图转为8位图
  13. 理查德·克莱德曼钢琴曲全集(梦中的婚礼)
  14. js 打开指定的浏览器_如何实现一个谷歌浏览器插件
  15. java 爬虫抢票_抢票爬虫 - nkandkn - 博客园
  16. AD9的pcb 里面怎样才能从TOP层视图换成从BOTTOM层网上面看,相当于把板子翻过来看
  17. 英语介绍计算机,计算机英语自我介绍
  18. vue倒计时:天时分秒
  19. 这5款微信小程序,实用又不占内存!
  20. Golang 期权 指标计算

热门文章

  1. 台式计算机dvd光驱在哪里,台式电脑怎么弹出光驱_台式电脑怎么弹出dvd
  2. PC程序(Electron)开发如何注入Cookie
  3. 某某网站JS逆向及tls指纹绕过分析
  4. springboot排错
  5. 诗经与当代流行歌曲相通之处—重章叠句
  6. [C++]已知f(x) = cosx - x。x的初值为3.14159/4,用牛顿法求解方程f(x) = 0的近似解,要求精确到10-6。f(x)的牛顿法的表达式为xn+1 = xn + (cosxn
  7. php面向对象三大特性,PHP面向对象的三大特性之一封装性
  8. VSCode 保存时 ESLint 一键修复
  9. svn用户名和密码都正确,但是登录不了
  10. MacBook Pro M1 Parallels Desktop Ubuntu 共享网络设置静态 IP