CPU是如何解决冒险问题的?(下)

简介: CPU是如何解决冒险问题的?

先读后写(Write After Read)

这次我们先计算 a = b + a,然后再计算 b = a + b。

int main() {
  int a = 1;
  int b = 2;
  a = b + a;
  b = a + b;
}
int main() {
   0:   55                      push   rbp
   1:   48 89 e5                mov    rbp,rsp
   int a = 1;
   4:   c7 45 fc 01 00 00 00    mov    DWORD PTR [rbp-0x4],0x1
   int b = 2;
   b:   c7 45 f8 02 00 00 00    mov    DWORD PTR [rbp-0x8],0x2
   a = b + a;
  12:   8b 45 f8                mov    eax,DWORD PTR [rbp-0x8]
  15:   01 45 fc                add    DWORD PTR [rbp-0x4],eax
   b = a + b;
  18:   8b 45 fc                mov    eax,DWORD PTR [rbp-0x4]
  1b:   01 45 f8                add    DWORD PTR [rbp-0x8],eax
}
  1e:   5d                      pop    rbp
  1f:   c3                      ret       

内存地址为15的汇编指令里,要把 eax 寄存器值读出,加到 rbp-0x4 的内存地址里。

在内存地址为18的汇编指令里,再写入更新 eax 寄存器里面。


如果在内存地址18的eax的写入先完成了,在内存地址为15的代码里面取出 eax 才发生,程序计算就错。这里,我们同样要保障对于eax的先读后写的操作顺序。


这个先读后写的依赖,一般被叫作反依赖,Anti-Dependency。

写后再写(Write After Write)

先设置变量 a = 1,再设置变量 a = 2。

int main() {
  int a = 1;
  a = 2;
}
int main() {
   0:   55                      push   rbp
   1:   48 89 e5                mov    rbp,rsp
  int a = 1;
   4:   c7 45 fc 01 00 00 00    mov    DWORD PTR [rbp-0x4],0x1
  a = 2;
   b:   c7 45 fc 02 00 00 00    mov    DWORD PTR [rbp-0x4],0x2
}

内存地址4所在的指令和内存地址b所在的指令,都是将对应的数据写入到 rbp-0x4 的内存地址里面。

如果内存地址b的指令在内存地址4的指令之后写入。那么这些指令完成之后,rbp-0x4 里的数据就是错误的。这就会导致后续需要使用这个内存地址里的数据指令,没有办法拿到正确的值。

所以,也需要保障内存地址4的指令的写入,在内存地址b的指令的写入之前完成。


这个写后再写的依赖,叫输出依赖,Output Dependency。

流水线停顿

除了读之后再进行读,对同一寄存器或内存地址的操作,都有明确强制顺序。而这个顺序操作的要求,也为使用流水线带来挑战。

因为流水线架构的核心,就是在前一个指令还没有结束时,后面的指令就要开始执行。


所以,需要有解决这些数据冒险的办法。

最简单也是最笨的就是流水线停顿(Pipeline Stall),或流水线冒泡(Pipeline Bubbling)。


若发现后面执行的指令,会对前面执行的指令有数据层面的依赖关系,就“再等等”。

进行指令译码时,会拿到对应指令所需访问的寄存器和内存地址,这时就能判断这个指令是否会触发数据冒险。

会触发,就能决定让整个流水线停顿一或者多周期。

image.png

时钟信号会不停地在0、1之间自动切换。所以,其实没法真停顿,流水线的每个操作步骤必须要干点事。

所以,实际上并非让流水线真停下来,而是在执行后续操作步骤前,插入一个NOP操作,即执行一个只负责摸鱼的操作。

image.png

这插入的指令,就好像一个水管(Pipeline)里进了个空气泡。在水流经过时,并没有真的传送水到下一个步骤,而是给了个啥都没有的空气泡,因此得名流水线冒泡(Pipeline Bubble)。

总结

  • 可通过增加资源解决结构冒险问题。
    现代CPU体系结构,也是在冯·诺依曼体系结构下,借鉴哈佛结构的一个混合结构解决方案。内存虽然没有按功能拆分,但在高速缓存层面拆分成指令缓存和数据缓存,从硬件层面,使得同一个时钟下对于相同资源的竞争不再发生。
  • 也可通过“等待”,即插入NOP操作解决冒险问题,即流水线停顿。
    不过,流水线停顿这样的解决方案要牺牲CPU性能。因为,实际上在最差的情况下,我们的流水线架构的CPU,又会退化成单指令周期的CPU。



参考

  • 《计算机组成与设计:硬件/软件接口》的第4.5~4.7章
目录
相关文章
|
8月前
|
运维 监控 负载均衡
震惊!线上四台机器同一时间全部 OOM,到底发生了什么?
震惊!线上四台机器同一时间全部 OOM,到底发生了什么?
|
4月前
|
人工智能 数据安全/隐私保护
意外之喜!5款小巧工具助你轻松面对繁忙生活
在繁忙的日常中,简单而巧妙的小工具能够带来意外的惊喜。这五款工具或许正是你所需要的,不妨一试。
34 1
|
9月前
|
SQL 缓存 监控
掌握了这些优化技巧,再也不用担心接口性能上不去了!
优化接口性能对每个后端开发同学来说见惯不惯了,也是一项必备的技能,因为我们平时开发中都会对外提供接口,性能差的话,功能多少会有影响。
|
9月前
|
Java
项目实战20—内存长期占用导致系统缓慢
项目实战20—内存长期占用导致系统缓慢
68 0
|
12月前
|
IDE Linux 调度
看完这篇文章,我再也不用担心线上出现 CPU 性能问题了(下)
在上一篇文章中咸鱼给大家介绍了 CPU 常见的性能指标,当生产环境出现 CPU 性能瓶颈的时候,优先观察这些指标有没有什么异常的地方,能解决大部分情况
|
12月前
|
存储 消息中间件 Linux
看完这篇文章,我再也不用担心线上出现 CPU 性能问题了(上)
生产环境上出现 CPU 性能问题是非常典型的一类问题,往往这个时候就比较考验相关人员排查问题的能力
|
编译器
【计算机组成原理】从CPU执行时间聊如何做性能优化
衡量性能的指标有什么?针对CPU执行时间,我们可以从哪些部分优化?
438 0
|
SQL 缓存 监控
坏代码导致的性能问题大赏:CPU占用飙到了900%!
坏代码导致的性能问题大赏:CPU占用飙到了900%!
坏代码导致的性能问题大赏:CPU占用飙到了900%!
|
Java 程序员 API
十分钟带你深入了解多线程—— Java虚拟机对锁优化所做的努力
十分钟带你深入了解多线程—— Java虚拟机对锁优化所做的努力
105 0
|
缓存 C语言
CPU是如何解决冒险问题的?(上)
CPU是如何解决冒险问题的?
334 0
CPU是如何解决冒险问题的?(上)

热门文章

最新文章

相关实验场景

更多