9

我正在研究我的 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% )

我对这些结果的解释是:

  • D 和 J 都是并行调度的。
  • J 具有 1 个周期的倒数吞吐量。
  • D 和 J 都被最优调度。

但是,我们也可以看到 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 是从 LSD 1发出的。
但是,LSD 从不发出 4 uOPs/c:

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

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


源代码在这个存储库中。


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

4

1 回答 1

7

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

与之前的微架构中的每个周期一个分支相比,重命名器每个周期可以分配两个分支。这可以消除执行中的一些气泡。

这是关于 Sandy 桥微架构的部分的一个小节。但据我所知,这适用于所有后来的微架构。最大重命名吞吐量为每个周期 4 微秒。但是最多可以有两个 uops 是分支。所以在这个所有微指令都是分支的例子中,LSD 在任何给定的循环中都不能提供超过 2 个微指令,即使在循环的第一次迭代中也是如此。

因此,每个周期将在 RS 中分配 2 个分支微指令,并且每个周期都可以分派两者(一个谓词采用和一个未采用)。所以 RS 占用率没有增长。

此限制不会影响您的程序的性能。每个周期执行 2 个分支微指令,每个周期的 IPC 为 3,这已经是最佳的了。

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

IDQ_UOPS_NOT_DELIVERED.CORE:计算分配器未使用的槽数。如果程序运行 C 核心周期,则插槽总数为 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:计算从 IDQ 发出最多 2 个微指令的周期数:测量值几乎等于 T。

IDQ_UOPS_NOT_DELIVERED.CYCLES_LE_3_UOP_DELIV.CORE:计算从 IDQ 发出最多 3 个微指令的周期数:测量值几乎等于 T。

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

请注意,Haswell 和 Skylake 中的 RS 拥有未融合的微指令。所以每个条目都可以包含一个未融合的微指令。见脚注 2。但这在这里无关紧要,因为没有微融合。

于 2018-08-28T22:44:29.867 回答