在x86 CPU上,哪些指令会导致分支预测错误?

4

这里有一个测试问题。

当流水线无法预测进一步执行方式(分支预测)时,哪些指令可能会潜在地减缓处理器的工作?

可能的答案: JGE | ADD | SUB | PUSH | JMP | JNZ | MUL | JG | CALL

如果我们谈论分支预测,JGE、JMP、JNZ和JG是可行的吗?


1
基本上是的。 - ecm
@ecm 等等,那么间接分支和间接调用呢,比如 JMP EAX / CALL EAX?当 EAX 在现场使用某些过度公式进行计算时,间接跳转和调用肯定会产生流水线气泡。 - hidefromkgb
@ecm和@hidefrom:即使是直接的jmp rel32(和call),也可能在前端早期被错误预测,需要重新指引。请参见Slow jmp-instruction。但是,间接的jmp/call肯定需要分支预测,并且可能一直到后端才能检测到错误预测,因此只有寄存器或内存间接形式需要在后端回滚uops。 - Peter Cordes
@hidefromkgb和@Peter Cordes:你们两个都是正确的。当然,我没有考虑到间接跳转和调用 - 也没有考虑返回指令,这是另一种可以说是间接分支的类型。关于慢jmp的有趣链接。 - ecm
1
@ecm:是的,“ret”很有趣,因为它通常与“call”匹配,所以CPU通常有一个专门用于预测“ret”的预测器堆栈(如16或24个条目),在BTB中对其进行更好的预测。例如针对x86的http://blog.stuffedcow.net/2018/04/ras-microbenchmarks/.高性能ARM微架构也会为“bx lr”执行类似的操作。但无论如何,对我来说,更令人惊讶的事实是,即使是直接无条件分支也需要在获取阶段进行预测,甚至在解码之前。“ret”更清楚地属于间接分支。 - Peter Cordes
显示剩余2条评论
1个回答

8

mul这样的指令没有特殊对EIP的操作,当然不会出现错误预测,但每一种跳转/调用/分支在流水线设计中都可能发生某种程度的错误预测,即使是简单的call rel32。这种影响在像现代x86 CPU这样高度流水线化的乱序执行设计中会非常严重。

是的,jcc条件分支总是需要预测;在解码时无法获得FLAGS的值,只能在执行时才能获取。

即使是直接的jmp rel8 / jmp rel32 (以及call rel32),需要在前端进行预测,甚至在它们被解码之前,这样取指令阶段就知道在获取可能包含跳转(无条件或预测为taken的条件跳转)的块后该获取哪个块(它不需要知道跳转类型,只需知道是否按直线获取)。如果有太多的BTB,参见Slow jmp-instruction了解更多关于简单无条件直接分支速度变慢的信息。
如果考虑像classic 5-stage RISC这样的简单顺序流水线,没有阶段之间的缓冲区,所有分支基本上是等价的:取指令阶段需要每个时钟周期获取1条指令来避免气泡。它需要在前一条指令仍在解码的同时知道下一个取指地址。更长的流水线会让这个问题变得更糟。
更简单的是,存在间接形式的jmp和call,如“jmp eax”或“jmp [edi]”,它们从寄存器或内存中加载新的EIP。这些显然需要预测;你有无限的可能性去猜测它将去哪里,不仅仅是taken或not-taken。
依赖于数据的分支(基于FLAGS的条件分支或基于寄存器或内存的间接分支)可以一直到后端(乱序执行)才发现错误预测。恢复可能需要丢弃来自错误路径的后续指令的执行结果,以及获取/解码正确路径。当skylake CPU错误预测分支时会发生什么? 但处理直接jmp/call的错误预测更简单:只需重新定向获取/解码阶段,因为在解码指令后目标地址就已知,而无需执行该指令。错误预测不会进入后端,因此只是前端的一个“bubble”。

有趣的事实: ret 也可能会出现错误预测; 它基本上是一种间接分支 (pop eip)。但是有些特殊的预测器利用了调用和返回指令之间的常规配对,保持最近调用记录的内部堆栈,以反映在内存中调用堆栈的使用情况。http://blog.stuffedcow.net/2018/04/ras-microbenchmarks/


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接