我正在研究 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 是从 LSD1 发出的。
然而,LSD 从未发出 4 uOPs/c:
7,591,866 lsd_cycles_active ( +- 3.17% )
0 lsd_cycles_4_uops
我的解释是,LSD 无法从下一次迭代中发出 uOP2,从而每个周期仅将 D J 对发送到 BE。
我的解释正确吗?
源代码在 this repository .
<小时/>1 有一点差异,我认为这是由于允许进行一些上下文切换的迭代次数较多。
2 这在电路深度有限的硬件中听起来相当复杂。
最佳答案
循环中的所有微指令都是分支(每次迭代 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 uops。但最多两个微指令可以是分支。因此,在这个所有微指令都是分支的示例中,即使在循环的第一次迭代中,LSD 在任何给定周期也永远无法提供超过 2 个微指令。
因此,每个周期将在 RS 中分配 2 个分支微指令,并且每个周期都可以调度这两个分支微指令(一个谓词采用,一个不采用)。所以RS占用率不会增长。
此限制不会影响程序的性能。每个周期执行 2 个分支 uops,每个周期提供 3 个 IPC,已经是最佳选择。
我试图找到一个可以捕获由于该限制而导致的分配器停顿的性能事件。事件 RESOURCE_STALLS.ANY
和 UOPS_ISSUED.ANY
(使用 cmask
=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个uop的周期数:测量值几乎等于T。
IDQ_UOPS_NOT_DELIVERED.CYCLES_LE_3_UOP_DELIV.CORE
:统计IDQ最多发送3个uop的周期数:测量值几乎等于T。
因此,由于执行时间几乎等于 T 个核心周期,因此我们可以得出结论,分配器在大多数周期中每个周期仅分配 2 个 uops,这等于调度率。
请注意,Haswell 和 Skylake 中的 RS 拥有未融合的微指令。因此每个条目可以容纳一个未融合的微指令。请参阅Footnote 2 。但这在这里并不重要,因为没有微融合。
关于assembly - LSD 能否从检测到的循环的下一次迭代中发出 uOP?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/52054585/