一般来说,当使用标志的指令读取一个或多个不是由最近的标志设置指令写入的标志时,会发生部分标志停止。
所以像inc 这样只设置一些标志(它没有设置CF)的指令不会固有地导致部分停顿,但会导致停顿如果 后续指令读取inc 未设置的标志(CF)(没有任何设置CF 标志的干预指令)。这也意味着写入所有有趣标志的指令永远不会涉及部分停顿,因为当它们是执行标志读取指令时最近的标志设置指令时,它们必须写入消耗的标志.
因此,一般而言,静态确定是否会发生部分标志停顿的算法是查看使用标志的每条指令(通常是 jcc 系列和 cmovcc 以及一些专门的指令,如 adc ) 然后向后走以找到设置 any 标志的第一条指令,并检查它是否设置了消费指令读取的所有标志。否则,将发生部分标志停止。
后来的架构,从 Sandy Bridge 开始,本身不会遭受部分标志stall,但仍然会受到指令添加到前端的额外 uop 形式的惩罚在某些情况下。与上面讨论的失速相比,这些规则略有不同,适用于范围更窄的案例。特别是,所谓的flag merging uop只有在一个flag消耗指令从多个flag读取并且这些flag最后被不同的指令设置时才被添加。这意味着,例如,检查单个标志的指令永远不会导致发出合并微指令。
从 Skylake 开始(可能从 Broadwell 开始),我没有发现 任何 合并 uops 的证据。相反,uop 格式已扩展到最多 3 个输入,这意味着单独重命名的进位标志和一起重命名的 SPAZO 组标志都可以用作大多数指令的输入。例外情况包括像 cmovbe 这样的指令,它有两个寄存器输入,其条件 be 需要使用 both C 标志和一个或多个 SPAZO 标志。然而,大多数条件移动只使用 C 和 SPAZO 标志中的一个或另一个,并且采用一个 uop。
示例
这里有一些例子。我们讨论了“[partial flag]stalls”和“merge uops”,但如上所述,两者中最多只有一个适用于任何给定的架构,所以像“以下导致停止并发出合并 uop”之类的内容应该被解读为“以下导致停顿 [在那些具有部分标志停顿的旧架构上] 或合并 uop [在那些使用合并 uops 的新架构上]”。
停止和合并uop
以下示例将导致在 Sandy Bridge 和 Ivy Bridge 上发出停顿和合并 uop,但不会在 Skylake 上发出:
add rbx, 5 ; sets CF, ZF, others
inc rax ; sets ZF, but not CF
ja label ; reads CF and ZF
ja 指令分别读取 CF 和 ZF 分别由 add 和 inc 指令设置,因此插入合并 uop 以统一单独设置的标志以供 @987654341 使用@。在停顿的架构上,发生停顿是因为ja 从CF 读取,而该CF 不是由最近的标志设置指令设置的。
仅摊位
add rbx, 5 ; sets CF, ZF, others
inc rax ; sets ZF, but not CF
jc label ; reads CF
这会导致停顿,因为在前面的示例中,CF 被读取,它不是由最后一个标志设置指令设置的(这里是inc)。在这种情况下,可以通过简单地交换inc 和add 的顺序来避免停顿,因为它们是独立的,然后jc 将只从最近的标志设置操作中读取。不需要合并 uop,因为读取的标志(仅 CF)都来自相同的 add 指令。
注意:此案例正在辩论中(请参阅 comments) - 但我无法对其进行测试,因为我在我的 Skylake 上根本找不到任何合并操作的证据。
没有停顿或合并uop
add rbx, 5 ; sets CF, ZF, others
inc rax ; sets ZF, but not CF
jnz label ; reads ZF
这里不需要停顿或合并 uop,即使最后一条指令 (inc) 只设置了一些标志,因为消费 jnz 只读取由inc 设置的标志(一个子集)并且没有其他。因此,这种常见的循环习语(通常使用dec 而不是inc)本身不会导致问题。
这是另一个不会导致任何停顿或合并 uop 的示例:
inc rax ; sets ZF, but not CF
add rbx, 5 ; sets CF, ZF, others
ja label ; reads CF and ZF
这里ja 确实读取了CF 和ZF,并且存在一个inc,它没有设置ZF(即,部分标志写入指令),但没有问题,因为add 位于 inc 之后并写入所有相关标志。
班次
移位指令sar、shr 和shl 在其可变计数和固定计数形式中的行为与上述不同(通常更差),并且这在不同架构之间变化很大。这可能是由于它们奇怪且不一致的标志处理1。例如,在许多架构上,当在计数不是 1 的移位指令之后读取 any 标志时,会出现部分标志停止的情况。即使在最新的架构上,变量移位的成本也很高,为 3由于标志处理而导致的 uops(但不再有“停顿”)。
我不会在此处包含所有血腥细节,但如果您想了解所有细节,我建议您在 Agner 的microarch doc 中查找 shift 这个词。
一些旋转指令在某些情况下也有有趣的标志相关行为,类似于移位。
1 例如,根据移位计数是 0、1 还是其他值来设置不同的标志子集。