一般而言,当一个消耗标志位的指令读取了一个或多个不是最近设置标志位的指令所写入的标志位时,会发生部分标志位停顿。
所以像inc
这样仅设置一些标志位的指令(它不设置CF
),并不会本质上导致部分停顿,但如果后续指令读取了inc
未设置的标志位(CF
)(而没有任何中间指令设置CF
标志位),会导致停顿。这也意味着写入所有有趣标志位的指令永远不会涉及到部分停顿,因为当它们在标志位读取指令被执行的那一点是最新的标志设置指令时,它们必须已经写入了被使用的标志位。
因此,通常用于静态确定是否会出现部分标志位停顿的算法是,查看每个使用标志位的指令(通常包括jcc
系列和cmovcc
以及一些特殊指令,如adc
),然后向后查找第一个设置任何标志位的指令,并检查它是否设置了消耗指令读取的所有标志位。如果没有,将发生部分标志位停顿。
从Sandy Bridge开始的后续架构并没有遭受部分标志位停顿,但某些情况下仍会因指令而增加一个额外的uop。规则略有不同,并且与上述停顿相比适用范围更窄。特别是,只有当消耗标志位的指令从多个标志位中读取,并且这些标志位是由不同的指令最后设置的时,才会添加所谓的标志合并uop。这意味着,例如,仅检查单个标志位的指令永远不会导致发出合并的uop。
从Skylake(和可能从Broadwell)开始,我找不到任何合并uops的证据。相反,uop格式已扩展为最多接受3个输入,这意味着重命名的进位标志和重新命名到一起的SPAZO组标志都可以用作大多数指令的输入。但像cmovbe
这样具有两个寄存器输入的指令以及其条件be
需要同时使用 C标志位和一个或多个SPAZO标志 。但是,大多数条件移动仅使用C和SPAZO标志之一,并且只占用一个uop。
示例
以下是一些示例。我们讨论"[partial flag] stalls"和"merge uops",但如上所述,任何给定体系结构最多只有其中之一适用,因此像"以下会导致停顿和合并的uop被发出"这样的语句应该读作"以下将在那些有部分标志位停顿的较旧体系结构上引起停顿,在那些使用合并uop而不是停顿的新型体系结构上引起合并uop"。
停顿和合并uop
add rbx, 5 ; sets CF, ZF, others
inc rax ; sets ZF, but not CF
ja label ; reads CF and ZF
ja
指令读取由add
和inc
指令最后设置的CF
和ZF
标志位,因此插入了合并uop以将分别设置的标志位统一为一个,以供ja
使用。在停顿体系结构上,由于ja
从最近的标志设置指令未设置的CF
中读取,因此会发生停顿。
仅停顿
add rbx, 5 ; sets CF, ZF, others
inc rax ; sets ZF, but not CF
jc label ; reads CF
这会导致停顿,因为与先前的例子一样,读取了未被最后一个标志设置指令(这里是inc)设置的CF。在这种情况下,可以通过交换inc和add的顺序来避免停顿,因为它们是独立的,然后jc只会从最近的标志设置操作中读取。不需要合并uop,因为读取的标志(仅CF)都来自同一个add指令。
注意:这个案例正在讨论中(请参见评论)-但我无法测试它,因为在我的Skylake上找不到任何合并操作的证据。
没有停顿或合并uop
add rbx, 5 ; sets CF, ZF, others
inc rax ; sets ZF, but not CF
jnz label ; reads ZF
这里不需要任何的停顿或合并单元,即使最后一条指令(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
,并且存在一个不会设置 ZF
的 inc
(即部分标志位写入指令),但没有问题,因为 add
在 inc
之后,并且写入了所有相关的标志位。
移位操作
sar
、shr
和 shl
指令,在它们的变量和固定计数形式中的行为与上述描述不同(通常更差),并且在不同的体系结构中差异较大。这可能是由于它们的奇怪和不一致的标志处理所致1。例如,在许多体系结构中,在使用除 1 以外的计数值进行移位指令后读取任何标志位时,会出现类似于部分标志位暂停的情况。即使在最新的体系结构上,变量移位也会由于标志处理而有显着的 3 uops 成本(但不再有“暂停”)。
我不会在这里列出所有详细信息,但如果您想要所有详细信息,我建议在 Agner 的微体系结构文档中查找单词“shift”。
有些旋转指令在某些情况下也具有与移位类似的标志相关行为。
1 例如,根据移位计数是否为 0、1 或其他值设置不同的标志子集。
add
怎么可能每个时钟周期有4个吞吐量呢?(是的,不同组的标志分别重命名,所以inc
也可以每个时钟周期有4个吞吐量,并且没有对FLAGS的输入依赖性,就像一些英特尔CPU可以将ah
与al
分别重命名写入一样。)正在努力回答,但请参阅Agner Fog的微架构指南:http://agner.org/optimize/。他解释了部分标志停顿和合并。 - Peter Cordes