什么是数据依赖?
指一条指令在执行之前依赖于顺序上更早的指令的结果。在采用流水线或超标量技术的 高性能处理器中,数据依赖性会导致处理器流水线的服务中断,或者阻止超标量处理器中指令的并行发出。
考虑同一程序中的两条指令 ik 和 ii,其中 ik 位于 ii 之前。如果 ik 和 ii 有一个公共寄存器或内存操作数,则它们相互之间存在数据依赖性,除非该公共操作数在两条指令中都用作源操作数。
一个例子是 ii 使用 ik 的结果作为源操作数。在顺序执行数据中,依赖性不会产生任何问题,因为指令是严格按照规定的顺序执行的。
数据依赖性既可以出现在后续指令之间的“直线代码”中,也可以出现在循环中,出现在循环的后续迭代所属的指令之间,如图所示。
因此,“直线代码”可以定义任何代码序列,甚至是循环体中的指令,这些指令不涉及后续循环迭代中的指令。直线代码可以包含三种不同类型的依赖性,称为 RAW(写后读)、WAR(读后写)和 WAW(写后写)依赖性。
直线代码中的数据依赖性
内存数据的依赖性可以用类似的方法解释。
RAW 依赖性 − 考虑两条汇编语言指令 −
i1: load r1, a; i2: add r2, r1, r1;
因此,指令 i2 使用 r1 作为源。结果,在 r1 由 i1 加载之前,i2 不能直接执行。因此,i2 依赖于 i1。RAW 依赖性也称为流依赖性。它们是真正的依赖性,因为它们不能被消除。
RAW 依赖性可以细分为加载使用和定义使用依赖性。在下面的例子中,需要先加载请求的源操作数。此方法可以处理加载使用依赖性。相比之下,如果请求的源操作数在指令序列中前一条指令中表示。
i1: mul r1, r4, r5; i2: add r2, r1, r1;
这种类型的 RAW 依赖性被称为定义使用依赖性。
WAR 依赖性 − 考虑执行以下指令 −
i1: mul r1, r2, r3; i2: add r2, r4, r5;
在这种情况下,i2 写入 r2,而 i1 使用 r2 作为源。如果出于任何原因,i2 在 i1 之前执行,则 r2 的初始内容将在 r1 读取它之前被重写,这会导致错误的结果。
WAW 依赖性:如果两条指令都写入相同的目标,则这两条指令是 WAW 依赖性(或输出依赖性),如下例所示:
i1: mul r1, r2, r3; i2: add r1, r4, r5;
这种类型的依赖性也是一种虚假依赖性,可以通过寄存器重命名以类似于 WAR 依赖性的方法消除。当包含的逻辑上先前的指令的执行完成并且产生的结果可以刷新程序状态时,会确定 WAW 依赖性。