【问题标题】:What is a Partial Flag Stall?什么是部分标志位?
【发布时间】:2018-09-26 19:12:33
【问题描述】:

我刚刚检查了this answer by Peter Cordes,他说,

部分标志停顿在读取标志时发生,如果它们发生的话。 P4 从来没有部分标志停顿,因为它们永远不需要被合并。相反,它具有错误的依赖关系。几个答案/ cmets 混淆了术语。他们描述了一个错误的依赖关系,但随后将其称为部分标志停止。这是由于仅写入一些标志而发生的减速,但术语“部分标志停顿”是在必须合并部分标志写入时在前 SnB 英特尔硬件上发生的情况。 Intel SnB 系列 CPU 插入一个额外的 uop 以合并标志而不会停止。 Nehalem 和更早的版本停滞了约 7 个周期。我不确定 AMD CPU 的损失有多大。

我觉得我还不明白什么是“部分旗帜摊位”。我怎么知道发生了什么?读取标志时,除了有时之外,还有什么触发事件?合并标志是什么意思?在什么情况下“写入了一些标志”但不发生部分标志合并?我需要了解哪些关于旗帜摊位的知识才能理解它们?

【问题讨论】:

  • Peter Cordes 和其他人可能有更全面的解释,但据我了解,标志位在寄存器重命名中单独重命名。对于设置所有标志位的指令,这是大多数,所有这些“寄存器”的状态可以一次全部重置,但对于只影响标志位子集的指令,实际标志值需要如果有意义,则从当前指令以及设置剩余标志位的最后一条指令合并。这种合并(有时)需要额外的时间。
  • 我的心智模型只是指令在一个全局标志寄存器上串行操作?这不是真的吗?期待彼得的回答,如果他嗡嗡作响。
  • @EvanCarroll:EFLAGS 当然更名了。如果你没有破坏the WAW hazardadd 怎么可能有 4 个时钟吞吐量? (是的,不同的标志组被分别重命名,因此inc 也可以有 4 个每个时钟吞吐量,并且对 FLAGS 没有输入依赖性,就像某些 Intel CPU 在写入时如何将ahal 分开重命名一样分开。)正在寻找答案,但请参阅 Agner Fog 的微架构指南:agner.org/optimize。他解释了部分标志停顿和合并。
  • 我要闭嘴等待答案。我不会骗你的名字 Amazon-d 几次。万一你出过一本关于 x86、Linux 或 Radare 的书,就拿我的钱吧。

标签: assembly x86 intel cpu-architecture


【解决方案1】:

修改 uop 的标志只能更新标志寄存器的一部分。 RAT 有一个用于 flags/eflags/rflags 寄存器的条目和一个掩码,该掩码显示由 uop 更改的标志,这些标志导致该条目指向的物理寄存器被分配。如果发生一系列读取和写入相同标志的指令,则为每次写入分配一个单独的物理寄存器,并且每次读取都使用前一个物理寄存器。在这些寄存器中将写入该标志,并且所有其他标志都将被清除。这就是为什么当从不在标志 RAT 条目中的掩码中的不同标志读取时不能使用当前物理寄存器的原因,因为它会读取一个清除位而不是已留下的标志的真实状态。在旧的微架构上,会发生停顿,直到标志寄存器的状态在 RRF 中有效(通过等待每个标志设置 uop 的退出,然后再插入它们在 RRF 标志寄存器中设置的位,其中每个 uop 被检查以知道它使用的架构寄存器/它改变的标志,这是一种比 x86 宏操作更容易解释的格式。

在使用 PRF 方案(SnB 以上)的微架构上,当没有专用的 RRF 寄存器时,需要一个合并的 uop 来保持一个统一的标志寄存器,否则退休 RAT 将指向一个无意义的物理寄存器,只有 1 个中的标志。合并 uop 发生在每个部分标志修改指令之后,如 incdecadd 修改所有 6 个状态标志,因此不需要合并微指令。我认为这可能意味着状态、控制和系统标志在 PRF 方案上分别重命名,因为 add 不需要合并 uop。显然 CF 标志是renamed differently to the SPAZO cluster

部分寄存器停顿是类似的。 The RAT has 2 entries to represent rax: an entry for al/ax/eax/rax (distinguished by a size indicator in the entry) and ah(在写入axeaxrax 时都会更新以指向同一个寄存器)。因为只有2个互斥寄存器,所以只需要2个就可以表示。如果对eax 的读取发生在对较小寄存器之一的先前写入退出之前,则分配器停止(因为 ROB 条目不能对同一操作数有 2 个依赖项),直到 RRF 中存在完整的寄存器,然后它会将这两个条目重命名为 rax 的 RRF 寄存器。

在后来使用 PRF 方案的微架构中,这现在很困难,因为不再保留用于 rax 的单个 RRF。所以需要使用merging uop,也恰好比之前微架构的stall方式快。

合并 uop 实现

  1. 合并微指令的一种实现可能是在每次写入部分标志/寄存器之前插入它,并且合并微指令在将其全部写入新的物理寄存器之前从完整寄存器/标志寄存器中读取。然后为写入分配相同的寄存器,这导致写入自然地合并到其中。随后的读取可以读取寄存器的任何部分/任何标志。这基本上在每个部分标志写入指令和之前的标志写入指令(部分或完整)之间以及在每个部分寄存器写入和之前(完整/部分)写入寄存器之间建立了依赖链。在这种情况下,RAT 永远不会有部分重命名。

  2. 它可以在写入部分寄存器后立即分配。合并微指令采用前一个物理寄存器(始终是完整的rax/eax 写入,或者在标志的情况下,完整的状态标志更新,就像add 或合并微指令所做的那样)和新的物理寄存器并将它们组合到新的物理寄存器中。这表明分配器插入它。如果它是由解码器插入的,则分配器可以在前一个 RAT 指针未知时在不同的周期中分配该 uop。

  3. 它可以在从 RAT 中具有统一状态的寄存器读取之前立即分配。这意味着 RAT 分别跟踪 rax/eaxaxalah。在这种情况下,需要合并的2个物理寄存器取自RAT。

优化手册暗示它是后两种情况之一“合并 uop 在每次部分寄存器写入之后发生”(即写入 axalahbut not eax)。

【讨论】:

  • How exactly do partial registers on Haswell/Skylake perform? Writing AL seems to have a false dependency on RAX, and AH is inconsistent 表明 AL / AX 在 Haswell(或者可能是 IvB)或更高版本中没有与 RAX 分开重命名,只有 AH。
  • @PeterCordes 在这个答案中,我说的是 al、eax 和 rax 共享一个条目,而 ah 有一个单独的条目
  • 我在可能的合并微操作实现结束时谈论您的列表。您将 RAT 跟踪 RAX 与 AX、AL 和 AH 分开讨论。但 HSW 简化了这一点。在此之前,mov al, ... 确实避免了对 RAX 旧值的错误依赖,因此有一些机制可以跟踪单独重命名的 AL 和 AH,它们都没有对 RAX 的错误依赖。 (IIRC,英特尔的优化手册提到 Sandybridge 在您进行 RMW 操作时选择不重命名 AL,例如 inc al。但对于只写访问,我认为它将单独重命名。)
  • @PeterCordes 如果您从 eax 读取,则先前对 ax/al 的写入需要退出,以便可以分配 ROB 条目。 RAT 会知道这一点,因为当它去重命名 eax 读取时,它会看到当前 al/ax/eax/rax 的宽度为 8 位或 16 位,因此它会停止直到 ax/al 写入指令的退出阶段使 RAT @ 987654352@ 32 位宽度的累加器 RRF 条目的入口点。当对 ah 的写入退出时,它会写入累加器 RRF 寄存器(只有一个)并声明它现在是 32 位宽度,因为在它之前没有指令要退出。
  • “3 循环合并 uop”描述来自 Agner Fog 的 microarch PDF,Core 2 / Nehalem 与 Pentium-M 及更早版本的改进。他没有说他是如何测量它的,但一个实验可能是两个长的 dep 链(例如 imul 延迟),一个在另一个的阴影中,在后面/较短的一个中有部分 reg-stall。如果它合并,您将不会看到每次循环迭代的周期总体增加,但如果它完全停止直到结果出现在 RRF 中,那么它也必须等待另一个 dep 链。 (IDK,如果他在旧 uarch 上的“5-6 周期”停顿是最好的情况或序列化。)
【解决方案2】:

一般来说,当使用标志的指令读取一个或多个不是由最近的标志设置指令写入的标志时,会发生部分标志停止。

所以像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 指令分别读取 CFZF 分别由 addinc 指令设置,因此插入合并 uop 以统一单独设置的标志以供 @987654341 使用@。在停顿的架构上,发生停顿是因为jaCF 读取,而该CF 不是由最近的标志设置指令设置的。

仅摊位

add rbx, 5   ; sets CF, ZF, others
inc rax      ; sets ZF, but not CF
jc  label    ; reads CF

这会导致停顿,因为在前面的示例中,CF 被读取,它不是由最后一个标志设置指令设置的(这里是inc)。在这种情况下,可以通过简单地交换incadd 的顺序来避免停顿,因为它们是独立的,然后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 确实读取了CFZF,并且存在一个inc,它没有设置ZF(即,部分标志写入指令),但没有问题,因为add 位于 inc 之后并写入所有相关标志。

班次

移位指令sarshrshl 在其可变计数和固定计数形式中的行为与上述不同(通常更差),并且这在不同架构之间变化很大。这可能是由于它们奇怪且不一致的标志处理1。例如,在许多架构上,当在计数不是 1 的移位指令之后读取 any 标志时,会出现部分标志停止的情况。即使在最新的架构上,变量移位的成本也很高,为 3由于标志处理而导致的 uops(但不再有“停顿”)。

我不会在此处包含所有血腥细节,但如果您想了解所有细节,我建议您在 Agner 的microarch doc 中查找 shift 这个词。

一些旋转指令在某些情况下也有有趣的标志相关行为,类似于移位。


1 例如,根据移位计数是 0、1 还是其他值来设置不同的标志子集。

【讨论】:

  • 我认为您的“仅摊位”示例仍然会在 Intel CPU 上产生合并 uop。我认为,如果您正在设计一个 CPU,它可以区分从一个较旧的 insn 读取 only 标志与混合编写器之间的区别,它将能够从单独重命名的标志组中读取 ZF包括 ZF 没有停滞或合并。就像英特尔 CPU 可以并行运行 inc alinc ah 而不会触发 EAX 的合并或停顿。但是对于标志,英特尔只是针对任何不走快速路径的合并案例。
  • @PeterCordes - 很奇怪,我写了some tests,但在这些情况中的任何中我都看不到额外合并操作的证据。我希望 1 有一个合并的 uop,2 是正在讨论的情况,3 我希望永远不会有一个合并的 uop,但我总是看到 inc,add,@987654373 的每个三元组总共有 3 个 uops @ 对于我检查的所有性能计数器,所有变体的性能都是相同的。我以为这些微指令会出现在性能计数器中?天湖。
  • @PeterCordes - 看到这个线程:似乎合并 uops 的实际发生可能比以前认为的要少得多,至少在 Skylake 上,但也许在早期的架构上也是如此(我只是没有它们测试)。请参阅this thread - 似乎发生的事情是额外的 uop 实际上是由于缺乏宏融合,因此在许多情况下没有额外的合并 uop(但仍然有额外的 uop)。除此之外,我没有进行太多调查,但 inc 完全有可能永远不会导致合并 uop。
  • 天哪,我从来没有注意到 cmovbecmova 是 SKL 上的 2-uop 指令。不过,从第一个操作数到目的地的延迟似乎仍然是 1 个周期。 cmp ebx, 123 / times 6 cmovbe ecx, ebx 循环体(仅通过 ECX 进行循环依赖)以每 6.5 个周期运行约 1 次迭代,而 cmovbcmovz 为 6.00。我认为您关于对 2 个标志具有单独输入的指令的结论听起来很可能,例如对于jbe
  • @PeterCordes - 奇怪的是,setbe 和朋友也是 2 微秒。奇怪的是,它们只有一个输入,所以如果 GP regs 和 flag regs 是可替代的,看起来这可能是 1 uop。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2011-04-29
  • 2011-02-05
  • 1970-01-01
  • 2011-12-04
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多