![a06acabaae7164bfe8bde1ccbe0b0fea.png](https://i-blog.csdnimg.cn/blog_migrate/384842318d528f8a429bfe0a80ee66dc.jpeg)
上一篇讲到,现在我们还有两个问题,第一,为什么上一步减少get_vec_element
的优化,看起来没有作用。第二,combine4
是否还有进一步优化的空间。
要回答这两个问题,我们需要先稍微了解一下现代处理器的一些内部操作流程。
5.7 理解现代处理器
前面都是些基本的优化操作:
- 降低循环的低效率
- 减少函数调用
- 消除存储器引用,使用临时变量
这几个方法都是处理器无关的,不依赖任何机器特性。接下来,就要利用处理器的一些处理方式来有正对性的优化了。
从汇编级来看,指令都是一条条执行,每条指令都包括从寄存器或者存储器取值,执行一个操作,并把结果放回寄存器或者存储器。但在实际的处理器中,是同时对多条指令求值的,这个现象称为指令级并行
,在某些设计中,可以有100或更多的指令在处理中。当然,处理器会有很多精细的机制来保证,指令看起来是简单的顺序执行的。
似乎进入了魔法领域
5.7.1 整体操作
下图是基于近期Intel处理器的一个近似结构,它的一个特性叫做超标量
,意思是它可以在每个时钟周期执行多个操作,而且是乱序的,即可能跟他们在机器级代码中的循序不一致。整个设计有两个主要部分ICU(Instuction Control Unit 指令控制单元)
和EU(Execution Unit 执行单元)
。
听到ICU是不是觉得虎躯一震。。。
![3e107fbb3e58064754d2fd795a368759.png](https://i-blog.csdnimg.cn/blog_migrate/9f9b97ff0ea1e9b18750211d669d002c.jpeg)
处理器在遇到分支跳转的时候,会采用一种分支预测
的技术,用来投机提前执行一些操作。预测就有可能错误,当预测错误的时候,提前执行的东西就浪费了,需要重新取出另一个方向上的指令。
指令解码将汇编指令转换成一组基本操作。例如addl %eax,%edx
产生一个加法操作,而addl %eax,8(%edx)
则会产生三个操作——加载内存,加法操作,存回内存。
如图5.11中,有一个退役单元
, 这个部分记录正在进行的操作。一旦一条指令操作完成了,而且所有引起这条指令的分支点也都被确认为预测正确,那么这条指令就可以退役了,所有对程序寄存器的更新都可以被实际执行了。反之,如果预测错误,则指令被清空,计算出的结果被抛弃。
任何对程序寄存器的更新都只会在指令退役的时候才会发生。
寄存器重命名
所谓寄存器重命名就是一种减少寄存器读写等待
的技术,一个要写寄存器的操作,可能因为是在预测分支中,无法直接写入,需要等待后续确认之后才能写入,那么该分支上后续指令需要使用这个寄存器,那么就在一张特定的表中取查询该值,从而跳过等待。即使没有分支预测,也能加快指令的执行,无需等待写入。
这个设计可以考虑怎么利用到项目中,对于那些更新数据会引起一连串变化的操作,可以进行数据缓存,把实际的更新延后。而取数据的时候需要先检测缓存。
5.7.2 功能单元的性能
下标是Intel Core i7参考机的一些算术性能。这里的容量是表示该运算的功能单元的数量。这个数量也是处理器能同时处理多个相同操作的基础。
![3a5b16d780992e6118a571cb4f7d7ccd.png](https://i-blog.csdnimg.cn/blog_migrate/5d05b723d57d38e1b647130ad0b3664d.png)
加法和乘法的发射时间都是1,这是通过流水线来实现的。当个操作没有结束就可以开始新的阶段操作。
除法运算时没有流水线实现的,它的发射时间等于延迟,说明新的运算需要等上一个运算结束后才开始。所以除法很慢。
功能单元的最大吞吐量为发射时间的倒数。容量可以增加吞吐量,例如容量为C,发射时间为I ,则吞吐量为 C / I
。而下文提到的吞吐量界限
则是吞吐量的CPE表示,即吞吐量的倒数。例如吞吐量为2,表示每个时钟周期能执行两个操作,即相应的CPE为0.5 。
5.7.3 处理器操作的抽象模型
前面combine4函数目前为止是最快的,这里对比下该函数与理论值的比较:可以看到距离理论值还有一定的距离,我们能达到理论值吗
![6c071007e92293533e243c0fcd1cc70d.png](https://i-blog.csdnimg.cn/blog_migrate/2940fba5d60b1ad33f17a79d9027c48f.png)