这是数据危害的示例:
MOV AL , 25
MOV BL , 10
ADD CL , AL , BL
LOAD DL , CL
在此汇编代码中,我们可以看到LOAD DL指令, 仅当执行ADD CL,AL,BL指令时才能执行CL。 因此,这两个指令都依赖于CL寄存器。 为了处理数据危害问题,我们可以减少 还有每个周期的指令数。这样可以避免寄存器之间的依赖性。
这是冲突危险(分支危险)的示例:
MOV AL , 25
MOV BL , 10
CALL branchement
LOAD DL , BL
ADD DL , AL
在此汇编代码中,两条指令LOAD AD,BL和ADD DL, AL甚至在执行指令之前就已经在管道中 呼叫。在这种情况下,不满足进行连接的条件,这会导致控制危险。 为防止此类冲突再次发生,建议您 在加载新指令之前清空管道
我想用同一代码查看两种危害(控制危害+数据危害)的示例
编辑:
混合2给我这个:
MOV AL , 25 ;
MOV BL , 10 ;
CALL branchement
ADD DL , AL
LOAD DL , BL
答案 0 :(得分:1)
Actual 8086根本没有流水线(预取除外);它是微码的。在开始下一条指令的解码之前,它完成了对一条指令的写回;唯一的危害是分支之后丢弃预取缓冲区。
x86指令很难流水线化(尤其是内存目标);直到486 / Pentium才真正完成,然后复杂的指令将使有序流水线停滞(基本上是一条指令中的危险,例如add [edx], eax
或pop eax
)。直到Pentium Pro(P6微体系结构)才可以有效地处理类似的指令(通过解码到1个或更多的微指令,并通过乱序的exec处理这些指令)。请参阅Agner Fog的微体系结构指南https://agner.org/optimize/
(实际的P6-系列和其他乱序的exec x86微体系结构通过寄存器重命名来隐藏WAW和WAR危险。请参见Why does mulss take only 3 cycles on Haswell, different from Agner's instruction tables? (Unrolling FP loops with multiple accumulators)和Deoptimizing a program for the pipeline in Intel Sandybridge-family CPUs。)
您显示的代码不是严格的x86;没有LOAD
助记符; x86的纯加载指令称为mov
。同样,LOAD DL, BL
没有任何意义;两个操作数都不能寻址内存;它们只是8位寄存器。如果要在寄存器之间复制,那也就是mov dl, bl
。
我想在同一代码中看到两种危害(控制危害+数据危害)的示例
一个简单的示例是一个间接分支(控制危险),其目标最近被写入(真正的RAW数据依赖性)。
例如如果我们采用16位模式(因为您提到8086):
push offset target ; modifies SP (the stack pointer), then stores to memory at SS:SP
ret ; ordinary near return = pop ip
target:
push 123
ret
有2个输入:
RET写SP(战争危险,尽管RET本身是最后一个读者)。如果我们认为推入和退回都写SP,也请WAW。
RET使用从内存加载的地址进行间接跳转(基本上是pop ip
)(如果有管道,则存在控制危险)。当前所有的CPU都会错误地预测ret
,因为它们具有特殊的调用/退出预测变量堆栈,该堆栈假定ret
会跳转到匹配的call
的返回地址,就像正常的代码用法一样。 (http://blog.stuffedcow.net/2018/04/ras-microbenchmarks/)
到达目标地址的push 123
如果您只想查看ret / push对,请在push
之后放置一个ret
,并压入一个可能错误预测的分支的“影子”。
当然,具有存储转发功能的存储缓冲区可以隐藏/处理内存数据危害,从而有效地重命名内存/缓存位置。 (x86的内存排序模型基本上是程序顺序+具有存储转发功能的存储缓冲区:允许内核在全局可见之前重新加载其自己的存储。)
现代x86 CPU使用“堆栈引擎”通过堆栈指针处理RAW数据依赖关系链,该堆栈引擎可以跟踪每个时钟周期到堆栈指针的多个偏移量。 (同样重要的是,无需额外的uop即可在后端实际对E / RSP进行添加,因此push
/ pop
可以是单个uop。)因此,这实际上是一种替代零延迟机制,用于执行堆栈指令的堆栈指针修改部分。直接使用E / RSP(例如mov bp, sp
)会导致堆栈同步uop(在Intel CPU上),将保存的偏移量清零并将其应用于后端的值。如果偏移量不为零。