回答
一个现代的无序处理器有一个重排序缓冲区(ROB),它可以跟踪所有的飞行指令并保持程序顺序。一旦ROB头部的指令完成,它将从ROB中清除。现代ROB是大约100-200个条目。同样,一个现代的OoO处理器有一个加载/存储队列,用于跟踪所有内存指令的状态。
最后,获取并解码但尚未执行的指令位于称为“问题队列/窗口”(或“保留站”)的地方,具体取决于设计人员的术语,这个问题在很大程度上与这个问题无关)。处于“问题队列”中的指令具有它们所依赖的寄存器操作数的列表,以及它们的操作数是否“繁忙”。一旦它们的所有寄存器操作数不再繁忙,指令就准备好执行并且它请求被“发出”。
问题调度程序从已准备好的指令中选取并将它们发给执行单元(这是无序的部分)。
让我们看看下面的顺序:
addi x1 <- x2 + x3
ld x2 0(x1)
sub x3 <- x2 - x4
正如我们所看到的,“子”指令依赖于先前加载指令(由寄存器“X2”的方式)。加载指令将被发送到内存并在高速缓存中未命中。它可能需要100多个周期才能返回并将结果写回到x2。同时,子指令将被放置在问题队列中,其操作数“x2”被标记为繁忙。它会坐在那里等待很长很长的时间。 ROB将迅速填满预测的指令,然后停止。整个核心将停止并旋转它的拇指。
一旦加载返回,它将写回“x2”,将这个事实广播到问题队列中,子听到“x2现在已准备就绪!”。并且子终端可以最终进行,ld指令最终可以提交,并且ROB将开始清空,从而可以提取新指令并将其插入到ROB中。
很明显,这会导致一个空闲的管道,因为很多指令都会被备份,等待负载返回。这有几个解决方案。
一个想法是简单地将整个线程切换到新线程。在一个简单的解释中,这基本上意味着清空整个流水线,将线程的PC(指向加载指令)和提交的寄存器文件的状态(在加载指令之前的加载)。通过缓存未命中来安排新线程有很多工作要做。呸。
另一种解决方案是同时多线程。对于双向SMT机器,您有两台PC和两个架构寄存器文件(即,您必须复制每个线程的架构状态,但可以共享微架构资源)。通过这种方式,一旦您为特定线程提取并解码指令,它们对于后端来说就是一样的。因此,尽管“子”指令会一直等待问题队列中的负载回来,但另一个线程可以继续前进。随着第一个线程停顿,可以为第二个线程分配更多资源(获取带宽,解码带宽,问题带宽等)。通过这种方式,管道通过毫不费力地填充第二个线程而保持繁忙。
在现代CPU中同时存在很多很多事情。当然,任何需要内存访问的结果都无法继续,但可能还有很多事情要做。假设以下C代码:
double sum = 0.0;
for (int i = 0; i < 4; ++i) sum += a [i];
if (sum > 10.0) call_some_function();
并假设读取数组a失速。由于读取[0]失速,加法和+ = a [0]将失速。但是,处理器继续执行其他指令。就像增加我一样,检查我是否正在循环读取一个[1]。这也是停顿,第二个加法和= + a [1]失速 - 这次是因为既没有正确的总和值也没有知道值a [1],但是事情继续进行,最终代码达到了语句“if(和> 10.0)“。
此时处理器不知道总和是多少。然而,它可以根据以前的分支发生的情况猜测结果,并开始执行call_some_function()推测性的函数。所以它会继续运行,但要小心:当call_some_function()将内容存储到内存中时,它不会发生。
最终读取[0]成功后,许多周期后。当发生这种情况时,它将被加入总和,然后将[1]加到总和中,然后a [2],然后a [3],然后比较总和> 10.0将正确执行。然后决定分支将变得正确或不正确。如果不正确,call_some_function()的所有结果都将被丢弃。如果正确,call_some_function()的所有结果都会从推测结果转换为实际结果。
如果失速时间过长,处理器将最终用完所有的事情。它可以很容易地处理四个增加和一个无法执行的比较,但最终它太多了,处理器必须停止。然而,在一个超线程系统中,你有另一个线程可以继续快乐地运行,并且以更高的速度运行,因为没有其他人使用内核,所以整个内核仍然可以继续执行有用的工作。
我认为你选择了一个很好的例子。这种投机行为是我没有意识到的。你有什么好的参考? – 2014-09-03 11:20:21
这里有一篇关于英特尔Haswell处理器的优秀文章:http://www.realworldtech.com/haswell-cpu/,它解释了现代处理器为提高速度所做的大量工作。维基百科文章http://en.wikipedia.org/wiki/Speculative_execution会有所帮助。 – gnasher729 2014-10-14 20:36:57
尼斯裁判感谢。投机执行是我没有从你的原始解释中吸取的魔法词! – 2014-10-15 09:31:59
- 1. 看看预处理器做了什么
- 2. 等待iPhone内存管理
- 3. 等待处理
- 4. 内存管理:我在这里做错了什么?
- 5. 我在处理这个Scala集合时做了什么错误?
- 6. 如何让webdriver等待,并做了什么声明?
- 7. while循环在等待消息时做些什么
- 8. 如何让对象等待几毫秒,然后在等待时间内取消主动等待?
- 9. Android:NoActionBar在主题中做了什么?
- 10. 处理内存不足错误的最佳做法是什么?
- 11. 线程在等待某事时发生了什么
- 12. '@@'在sqlplus提示符上做了什么?
- 13. Objective C预处理器/编译器/ Iphone SDK在幕后做了什么?
- 14. 文本等待处理
- 15. 羊群处理时发生了什么?
- 16. 提取通知待处理的辅助
- 17. 当你等待代码编译时,你通常会做什么
- 18. java:datainputstream:读等待数据时读取调用占用处理器时间吗?
- 19. 有什么方法可以等待接收者处理PostMessage?
- 20. 什么是等待?
- 21. Python多重处理 - 执行时间增加了,我做错了什么?
- 22. jg指令在经典的英特尔处理器上做了什么?
- 23. DAC卡在等待安装者处理
- 24. 此javascript正则表达式代码在处理URLS时做了什么?
- 25. 异步/等待方法和异常处理的最佳做法
- 26. 在调用git diff时,提交hash后做了什么?(^!)?
- 27. NSZoneFree在 - (void)dealloc中处理了什么?
- 28. 如何显示提交做了什么?
- 29. Dojo提取,如何等待两个同时异步提取?
- 30. 我做错了什么?异常在线程“主要” java.lang.NoSuchMethodError:主要
也许这个问题应该去http://cs.stackexchange.com/ – 2014-08-29 07:30:05
我的理解是,超线程CPU可以执行其他任务,而内存正在被提取。此外,其他线程可以执行任意数量的操作,只要它们不使用由内存提取操作占用的总线。实际上,今天大多数内存总线都是串行的(这是否正确?),所以只有一个CPU可能一次能够访问主内存。 – arman 2014-08-29 07:55:15
*我听说过切换到另一个线程的想法*根据语言,可以使用'std :: thread'(在C++中)切换到另一个线程。移动到另一个线程所需的时间以及将L1/L2内存复制到该线程所需的时间可能会超过典型的缓存行抓取时间,所以我不认为这是自动修复。 – arman 2014-08-29 07:58:04