3

我使用此代码来测试 IvyBridge 上循环迭代中依赖项的影响:

global _start
_start:
    mov rcx,    1000000000
.for_loop:          
    inc rax     ; uop A
    inc rax     ; uop B
    dec rcx     ; uop C
    jnz .for_loop   

    xor rdi,    rdi
    mov rax,    60  ; _exit(0)
    syscall

由于dec并且jnz将被宏融合到单个 uop,因此我的循环中有 3 个 uop,它们在注释中被标记。

uop B 依赖于 uop A,所以我认为执行会是这样的:

A C
B A C  ; the previous B and current A can be in the same cycle
B A C
...
B A C
B

因此,每个迭代可以执行循环 1 个周期。

但是,该perf工具显示:

 2,009,704,779      cycles                
 1,008,054,984      stalled-cycles-frontend   #   50.16% frontend cycles idl

所以它是每个迭代 2 个周期,并且有 50% 的前端周期空闲。

是什么导致前端 50% 空闲?为什么假设的执行图无法实现?

4

1 回答 1

3

B 和 A 形成一个循环携带的依赖链下一次迭代中的A不能运行,直到它有上一次B的结果。

任何给定的B都不能在与A相同的循环中运行:如果较早的还没有产生结果,那么后面的将使用什么输入?

该链长 2 个周期(每次迭代),因为 的延迟inc为 1 个周期。这会在后端造成无序执行无法隐藏的延迟瓶颈。(除了非常低的迭代次数,它可以在循环后与代码重叠)。

就像你完全展开一个巨大的链一样times 102400 inc eax,CPU 无法在每条指令链之间找到指令级并行性,而每条指令链都依赖于前一条。

宏融合dec rcx/jnzuop 独立于 RAX 链,是一个较短的链(每次迭代只有 1 个周期,只有 1 个 dec&branch uop,延迟为 1c)。因此它可以与 B 或 A 微指令并行运行。


有关指令级并行性和依赖链的概念以及 CPU 如何利用这种并行性在独立时并行运行指令的更多信息,请参阅我对另一个问题的回答

Agner Fog 的 microarch PDF 在早期章节中通过示例展示了这一点:第 2 章:乱序执行(除 P1、PMMX 之外的所有处理器)


如果每次迭代都启动一个新的 2 周期 dep 链,它会按预期运行。每次迭代都分叉的新链将为 CPU 暴露指令级并行性,以防止AB同时进行不同的迭代。

.for_loop:
    xor eax,eax          ; dependency-breaking for RAX
    inc rax     ; uop A
    inc rax     ; uop B
    dec rcx     ; uop C
    jnz .for_loop   

Sandybridge 系列在没有执行单元的情况下处理异或归零,因此在循环中这仍然只有 3 个未融合域 uop,因此 IvyBridge 有足够的 ALU 执行端口来在一个周期内运行所有 3 个。这也以每个时钟 4 个融合域微指令最大化了前端。

或者,如果您更改 A 以在 RAX 中启动一个新的 dep 链,并使用任何无条件覆盖 RAX 的指令而不依赖inc.

    lea  rax, [rdx + rdx]     ; no dependency on B from last iter
    inc  rax                  ; uop B

除了一对不幸的输出依赖的指令:为什么打破 LZCNT 的“输出依赖”很重要?

    popcnt rax, rdx        ; false dependency on RAX, 3 cycle latency
    inc  rax               ; uop B

在 Intel CPU 上,只有popcntlzcnt/tzcnt无缘无故地具有输出依赖性。bsf这是因为它们使用与/相同的执行单元bsr,如果输入为零,则在 Intel 和 AMD CPU 上保持目标不变。如果BSF / BSR的输入为零,英特尔仍然仅在纸上将其记录为未定义,但他们构建了实现更强保证的硬件。(AMD 甚至确实记录了这种 BSF/BSR 行为。)无论如何,英特尔的 BSF/BSR 就像 CMOV,并且需要目标作为输入,以防源 reg 为 0。 popcnt(以及前 Skylake 上的 lzcnt/tzcnt)受苦也由此而来。


如果您使循环超过 5 个融合域微指令,则 SnB/IvB 最多可以从前端每 2 个循环发出 1 个。Haswell 和后来在循环缓冲区中“展开”或其他东西,因此 5 uop 循环每次迭代可以以 ~1.25 c 运行,但 SnB/IvB 不能。 执行 uop 计数不是处理器宽度倍数的循环时性能会降低吗?

自 Core 2 以来,Intel CPU 的前端问题/重命名阶段为 4 个融合域 uops 宽。

于 2019-01-05T01:24:49.107 回答