LSD 能否从检测到的循环的下一次迭代发出 uOP?

Can the LSD issue uOPs from the next iteration of the detected loop?

我正在研究我的 Haswell 端口 0 上分支单元的功能,从一个非常简单的循环开始:

BITS 64
GLOBAL _start

SECTION .text

_start:

 mov ecx, 10000000

.loop:

 dec ecx             ;|
  jz .end            ;| 1 uOP (call it D)

jmp .loop            ;| 1 uOP (call it J)

.end:
 mov eax, 60
 xor edi, edi
 syscall

使用perf我们看到循环运行在1c/iter

Performance counter stats for './main' (50 runs):

        10,001,055      uops_executed_port_port_6   ( +-  0.00% )
         9,999,973      uops_executed_port_port_0   ( +-  0.00% )
        10,015,414      cycles:u                    ( +-  0.02% )
                23      resource_stalls_rs          ( +- 64.05% )

我对这些结果的解释是:

但是,我们也可以看到 RS 永远不会充满。
它最多可以以 2 uOPs/c 的速率发送 uOP,但理论上可以获得 4 uOPs/c,从而在大约 30 c 内产生一个完整的 RS(对于大小为 60 个融合域条目的 RS) .

按我的理解,分支预测错误应该很少,uOP应该都来自LSD。
于是我看了一下FE:

     8,239,091      lsd_cycles_active ( +-  3.10% )
       989,320      idq_dsb_cycles    ( +- 23.47% )
     2,534,972      idq_mite_cycles   ( +- 15.43% )
         4,929      idq_ms_uops       ( +-  8.30% )

   0.007429733 seconds time elapsed   ( +-  1.79% )

确认 FE 是从 LSD1.
发出的 但是,LSD 永远不会发出 4 uOPs/c:

     7,591,866      lsd_cycles_active ( +-  3.17% )
             0      lsd_cycles_4_uops 

我的解释是LSD无法从下一次迭代2发出uOP,因此每个周期只发送D J对到BE。
我的解释正确吗?


源代码在this repository.


1 有一点差异,我认为这是由于允许进行某些上下文切换的大量迭代。
2 在电路深度有限的硬件中,这听起来相当复杂。

循环中的所有微指令都是 b运行ches(每次迭代 2 个)。我认为 `lsd_cycles_4_uops 为零的原因是重命名器的限制。根据英特尔优化手册第 2.4.3.1 节:

The renamer can allocate two branches each cycle, compared to one branch each cycle in the previous microarchitecture. This can eliminate some bubbles in execution.

这是 Sandy 桥微体系结构部分的一个小节。但据我所知,这适用于所有后来的微体系结构。最大重命名吞吐量为每个周期 4 微指令。但是最多两个微指令可以是 b运行ches。因此,在这个所有微指令都是 b运行 的示例中,即使在循环的第一次迭代中,LSD 也永远不会在任何给定周期内传送超过 2 微指令。

因此,每个周期在RS中分配2个b运行ch uops,并且每个周期都可以调度两者(一个被预测的和一个未被预测的)。所以 RS 占用率不会增长。

此限制不会影响程序的性能。每个周期执行 2 b运行ch uops,每个周期 3 个 IPC,已经是最优的。

我试图找到一个可以捕获由于该限制而导致的分配器停顿的性能事件。事件 RESOURCE_STALLS.ANYUOPS_ISSUED.ANYcmask=1 和 inv=1)在这种情况下似乎不相关。 @IwillnotexistIdonotexist 建议使用 IDQ_UOPS_NOT_DELIVERED.CORE。我在下面展示了性能事件及其所有受支持变体的结果。我还提供了这些事件的正确含义,因为手册是错误的。 T表示迭代次数。

IDQ_UOPS_NOT_DELIVERED.CORE:计算分配器未使用的槽数。如果程序运行为C core cycles,则slot总数为4*C。测量值几乎等于2*T。由于周期数为T,槽数为4*T,这意味着大约一半的问题槽没有被利用。

IDQ_UOPS_NOT_DELIVERED.CYCLES_0_UOPS_DELIV.CORE:统计从 IDQ 传送零微指令的周期数。测量值可以忽略不计。

IDQ_UOPS_NOT_DELIVERED.CYCLES_LE_1_UOP_DELIV.CORE:统计从 IDQ 传送最多 1 微码的周期数。测量值可以忽略不计。

IDQ_UOPS_NOT_DELIVERED.CYCLES_LE_2_UOP_DELIV.CORE:统计最多2微码从IDQ下发的周期数:测得的值几乎等于T。

IDQ_UOPS_NOT_DELIVERED.CYCLES_LE_3_UOP_DELIV.CORE:统计从IDQ最多下发3微码的周期数:测得的值几乎等于T。

因此,由于执行时间几乎等于T个核心周期,我们可以得出结论,分配器在大多数周期中每个周期只分配恰好2微指令,这等于调度率。

请注意,Haswell 和 Skylake 中的 RS 持有未融合的微指令。因此每个条目都可以包含一个未融合的 uop。参见 。但这在这里并不重要,因为没有微融合。