我认为您测量得很准确,并且解释是微架构的,而不是任何类型的测量错误。
我认为你的中低 T 结果支持以下结论:lfence
阻止前端甚至发出过去的lfence
直到所有先前的指令失效,而不是让两个链中的所有微指令都已发出并等待lfence
打开开关并让每个链中的乘法开始按交替周期进行调度。
(port1 将立即获得 edx,eax,empty,edx,eax,empty,... 对于 Skylake 的 3c 延迟/1c 吞吐量乘数,如果lfence
不会阻塞前端,并且开销不会随 T 缩放。)
你输了imul
当只有来自第一条链的微指令在调度程序中时的吞吐量,因为前端尚未咀嚼imul edx,edx
并循环分支。对于窗口末尾的相同数量的周期,管道大部分已耗尽,仅剩下来自第二条链的微指令。
开销增量看起来呈线性,直到 T=60 左右。我没有计算数字,但到那里的坡度看起来是合理的T * 0.25
发出第一条链的时钟与 3c 延迟执行瓶颈。 IE。增量增长速度可能是无围栏周期总数的 1/12.
所以(鉴于lfence
我在下面测量了开销),T
no_lfence cycles/iter ~= 3T # OoO exec finds all the parallelism
lfence cycles/iter ~= 3T + T/4 + 9.3 # lfence constant + front-end delay
delta ~= T/4 + 9.3
@Margaret 报道说T/4
比更适合2*T / 4
,但我预计在开始和结束时都为 T/4,三角洲的斜率总共为 2T/4。
大约 T=60 后,delta 增长得更快(但仍然是线性的),斜率大约等于总无栅栏周期,因此每 T 大约 3c。我认为在这一点上,调度程序(预订站)的大小限制了无序窗口。您可能在 Haswell 或 Sandybridge/IvyBridge 上进行了测试,(分别有 60 个条目或 54 个条目的调度程序 https://www.realworldtech.com/haswell-cpu/4/。 Skylake 的条目有 97 个(但并未完全统一;IIRC BeeOnRope 的测试表明并非所有条目都可用于任何类型的 uop。例如,有些条目特定于加载和/或存储。)
The RS tracks un-executed uops. Each RS entry holds 1 unfused-domain uop that's waiting for its inputs to be ready, and its execution port, before it can dispatch and leave the RS1.
After an lfence
, the front-end issues at 4 per clock while the back-end executes at 1 per 3 clocks, issuing 60 uops in ~15 cycles, during which time only 5 imul
instructions from the edx
chain have executed. (There's no load or store micro-fusion here, so every fused-domain uop from the front-end is still only 1 unfused-domain uop in the RS2.)
对于大T,RS很快就被填满,此时前端只能以后端的速度前进。 (对于小 T,我们进行下一次迭代lfence
在此之前,这就是前端停滞的原因)。当 T > RS_size 时,后端看不到任何来自eax
imul 链,直到后端有足够的进展edx
链条在RS中腾出了空间。就在这时,一imul
每个链可以每 3 个周期调度一次,而不仅仅是第 1 条或第 2 条链。
记得从第一部分开始,就在之后花费的时间lfence
只执行第一个链=之前的时间lfence
仅执行第二条链。这也适用于这里。
即使没有,我们也能得到一些这种效果lfence
,对于 T > RS_size,但是长链的两侧都有可能重叠。 ROB 至少是 RS 大小的两倍,因此当不因以下原因而停滞时,乱序窗口lfence
即使 T 略大于调度程序容量,也应该能够保持两条链持续运行。 (请记住,uop 在执行后立即离开 RS。我不确定这是否意味着他们必须finish执行并转发结果,或者只是开始执行,但这对于短 ALU 指令来说是一个微小的区别。一旦完成,只有 ROB 会按照程序顺序保留它们,直到他们退休。)
ROB 和寄存器文件不应限制无序窗口大小(http://blog.stuffedcow.net/2013/05/measuring-rob-capacity/ http://blog.stuffedcow.net/2013/05/measuring-rob-capacity/)在这个假设的情况下,或者在你的真实情况下。它们都应该很大。
阻塞前端是一个实现细节lfence
关于英特尔的 uarches。说明书上只说了后面的说明不能execute。该措辞将允许前端将它们全部发布/重命名到调度程序(预订站)和 ROB 中,同时lfence
仍在等待,只要没有人被分派到执行单元。
所以较弱lfence
可能会有平坦的开销,直到 T=RS_size,然后与您现在看到的 T>60 的斜率相同。(开销的恒定部分可能会更低。)
请注意,保证了条件/间接分支的推测执行lfence
适用于执行,而不是(据我所知)代码获取。仅仅触发代码获取对于 Spectre 或 Meltdown 攻击来说没有(据我所知)有用。可能用于检测其解码方式的定时侧通道可以告诉您有关获取的代码的一些信息......
我认为当相关 MSR 启用时,AMD 的 LFENCE 在实际 AMD CPU 上至少同样强大。 (LFENCE 是否在 AMD 处理器上进行序列化? https://stackoverflow.com/questions/51844886/is-lfence-serializing-on-amd-processors).
Extra lfence
高架:
你的结果很有趣,但我一点也不惊讶,因为有大量的持续开销lfence
本身(对于小 T),以及随 T 缩放的组件。
请记住lfence
不允许稍后的指令开始,直到较早的指令完成retired。这可能比其结果准备好旁路转发到其他执行单元(即正常延迟)至少晚几个周期/管道阶段。
因此,对于小 T 来说,通过要求结果不仅准备好,而且还写回寄存器文件,在链中添加额外的延迟绝对是很重要的。
可能需要一个额外的周期左右lfence
允许发出/重命名阶段在检测到其之前的最后一条指令退出后再次开始操作。问题/重命名过程需要多个阶段(周期),并且可能 lfence 会在start这样做,而不是在将微指令添加到核心的 OoO 部分之前的最后一步。
甚至背靠背lfence
根据 Agner Fog 的测试,SnB 系列本身具有 4 个周期吞吐量。阿格纳·福格报道 https://agner.org/optimize/2 个融合域微指令(没有未融合的),但在 Skylake 上,如果我只有 1 个融合域,我会以 6 个融合域(仍然没有未融合)来测量它lfence
。但随着更多lfence
背靠背,uop 更少了!下降至约 2 uop/lfence
有很多背靠背,这就是阿格纳的衡量标准。
lfence
/dec
/jnz
(没有工作的紧密循环)在 SKL 上每约 10 个周期运行 1 次迭代,因此这可能会让我们了解真正的额外延迟lfence
即使没有前端和 RS-full 瓶颈,也会添加到 dep 链中。
测量lfence
开销仅one配送链, OoO exec 不相关:
.loop:
;mfence ; mfence here: ~62.3c (with no lfence)
lfence ; lfence here: ~39.3c
times 10 imul eax,eax ; with no lfence: 30.0c
; lfence ; lfence here: ~39.6c
dec ecx
jnz .loop
Without lfence
,以预期的每迭代 30.0c 运行。和lfence
,每迭代运行温度约为 39.3c,因此lfence
有效地为关键路径 dep 链增加了约 9.3c 的“额外延迟”。 (还有 6 个额外的融合域微指令)。
With lfence
在 imul 链之后、循环分支之前,它的速度稍微慢一些。但并不是整个周期变慢,因此这表明前端在单个问题组中发出循环分支 + 和 imullfence
允许恢复执行。既然如此,我不知道为什么它会慢。这不是来自分支未命中。
获得您期望的行为:
按照程序顺序交错链,就像 @BeeOnRope 在注释中建议的那样,不需要乱序执行来利用 ILP,所以它非常简单:
.loop:
lfence ; at the top of the loop is the lowest-overhead place.
%rep T
imul eax,eax
imul edx,edx
%endrep
dec ecx
jnz .loop
你可以放一对短的times 8 imul
a 内的链%rep
让OoO exec 过得轻松。
脚注1:前端/RS/ROB如何交互
我的思维模型是前端的问题/重命名/分配阶段向两个 RS 添加新的 uopsandROB 同时。
Uop 执行后离开 RS,但保留在 ROB 中直到按顺序退出。 ROB 可能很大,因为它永远不会无序扫描以查找第一个就绪的微指令,而只会按顺序扫描以检查最旧的微指令是否已完成执行并准备好退出。
(我假设 ROB 实际上是一个具有开始/结束索引的循环缓冲区,而不是一个实际上每个周期都将微指令复制到右侧的队列。但只需将其视为具有固定最大大小的队列/列表,其中前端在前面添加 uops,并且只要完全执行完毕,退休逻辑就会从末尾退休/提交 uops,直到某个每个周期每个超线程的退休限制,这通常不是瓶颈。Skylake 确实增加了它以获得更好的效果超线程,每个逻辑线程每个时钟可能有 8 个。也许退休还意味着释放物理寄存器,这有助于 HT,因为当两个线程都处于活动状态时,ROB 本身是静态分区的。这就是每个逻辑线程的退休限制的原因。)
哎呀喜欢nop
, xor eax,eax
, or lfence
,在前端处理(不需要任何端口上的任何执行单元)添加only到 ROB,处于已执行状态。 (ROB 条目大概有一个位将其标记为准备退出与仍在等待执行完成。这就是我正在谈论的状态。对于 uopsdid需要一个执行端口,我假设 ROB 位是通过完成端口 https://stackoverflow.com/questions/791798/what-is-the-eu-in-x86-architecture-calculates-effective-address#comment64889806_11389785来自执行单元。并且相同的完成端口信号释放其 RS 条目。)
Uops 从问题到问题都保留在 ROB 中退休.
从发行到发行,Uops 一直保留在 RS 中执行. RS 在少数情况下可以重放微指令, e.g. 用于缓存行分割负载的另一半 https://stackoverflow.com/questions/45128763/how-can-i-accurately-benchmark-unaligned-access-speed-on-x86-64,或者如果它是在预期负载数据到达时进行调度的,但实际上它没有到达。 (缓存未命中或其他冲突,例如IvyBridge 上的指针追逐循环中附近的依赖存储对性能产生奇怪的影响。添加额外的负载会加快速度吗? https://stackoverflow.com/questions/54084992/weird-performance-effects-from-nearby-dependent-stores-in-a-pointer-chasing-loop.) 或者当加载端口推测它可以在开始 TLB 查找之前绕过 AGU 以缩短小偏移量的指针追逐延迟时 -当基址+偏移量与基址位于不同页面时是否会受到惩罚? https://stackoverflow.com/questions/52351397/is-there-a-penalty-when-baseoffset-is-in-a-different-page-than-the-base
所以我们知道 RS 无法在调度时立即删除 uop,因为它可能需要重播。 (甚至可能发生在消耗负载数据的非负载微指令上。)但是任何需要重放的推测都是短程的,而不是通过微指令链,因此一旦结果从执行单元的另一端出来,微指令就可以从 RS 中删除。这可能是完成端口所做的一部分,同时将结果放在旁路转发网络上。
脚注 2:微融合 uop 需要多少个 RS 条目?
TL:DR:P6 系列:RS 已熔合,SnB 系列:RS 未熔合。
微融合 uop 被发送到 Sandybridge 系列中的两个独立的 RS 条目,但只有 1 个 ROB 条目。 (假设发行前没有未层压,请参阅 Intel 优化手册的 HSW 2.3.5 节或 SnB 2.4.2.4 节,以及微融合和寻址模式 https://stackoverflow.com/questions/26046634/micro-fusion-and-addressing-modes。 Sandybridge 系列更紧凑的 uop 格式在所有情况下都无法表示 ROB 中的索引寻址模式。)
负载可以在 ALU uop 的其他操作数准备就绪之前独立分派。 (或者对于微融合存储,存储地址或存储数据微指令可以在其输入准备好时进行调度,而无需等待两者。)
我使用问题中的两深度链方法在 Skylake 上进行实验测试(RS 大小 = 97), 带有微熔or edi, [rdi]
vs. mov
+or
,以及另一个部门链rsi
. (完整测试代码,Godbolt 上的 NASM 语法 https://godbolt.org/#z:OYLghAFBqd5QCxAYwPYBMCmBRdBLAF1QCcAaPECAKxAEZSAbAQwDtQmBnDzAWwCMGAT1IdUAV2LJMIAOQBSAMwAhRUoDUBRQBECmDgQapUAB1VrOPALQM8LANZq5AJicASAgDoLzp46cA2Z381fA5OPzdNFz9AgI1OO24CNUtkNQU1VGRjTGIAMw9jQTV9JmTLTAIE1MNkO1I0Fl0ADwJLDgB3QmQEPQbjMUsePGBiMrxUFg5SYyZgTEs8pjEGAmnkQWQGPtt9YjFkAgmp0jETDgB9PC4xTHQvFmEz40vMZsxkMV17ggRiTCY6EYHHuzw4ag8AHofO4fHIAAwAQQRiKwSxWyX%2BDBRzgArHg8mi1BcLgBNREAZQAshcAGrYABKJJRalZjmUbM5agAwgAFACqOI5bL5/LUtl0DBYJhZIoFPMmxFQmDUiKpWllrNFagpdkEzDsKrVGqReMwDG4OKcuLE3BKPCYxCqNmALBRTBdLB4GBVxn8pDU/gALFbcZgWPg8jjTdaCUSAJKI7mIi5UxEMgDSFNDDuQStZieTF1KTrU8M1rO9ADc1Jg%2BM0A7QmxWQnwy81gwH4R2AOxd5oATnLMbDEdzSpzTDzqALSZT4fQZZb1dr9YDLicLfQbe7nfb/j77aHoYX49QJ4tmFDaNsKsLKZLmhHN5Yd7nFwXJ4jBOjiOAhj4D1iUfFFiyqJ0QF/TkzDZVhFy5NliA4YwA0sYNQwJNRaArbYmAQ1liHwAM5FxJQ%2BDEKNcRNRFOVw/C1CQvBiNI8i8g4SQSOos1vyjJFORXNkSKUQi8E4gMRKgtkBLZOsUKw%2BEFMU4dkRHTDsKRD0RhYdJN1NYVWWQHhjFZAEG3bWgnAUINcWCdl1G3dsFC0VkODsPBjN%2BFU8jwJDkiDNQ%2BEEXRwUmDRejUbzfPFXQxiOUKUXvMDHSfREPEMExINNPT1DZI4eD0dJ4TUFcROY4T8E41kzCIZ01GYXQWA2NQg2QAMODwIztnMdB0H%2BLhbGAYqfQizg2lmX4hRy1k8oKhQisBRdGLKxjKrsjRUFq%2Brwya/xkEUAAxMAwAOo7WRakb9EscaEEyFghAikg6tQQFLEUbBDEBB7iA6R18DYA7Js5GbwTmzJiAIojHFIiSqKqjkaqA3a6rKbbBBO46FH2xxsqBjrZqKx6GMhoSJKcYT62cJQnHhdDYbWj1REDNItsa4Q1CrDgPCGmsul%2BMLrm63rvSwNQOnEBhFz4FVcV/PF/mMgAVDC8jUfb%2BQpbAK0eu4mKh8rRKoi9LT4qTUBrWsmDMkmKsNk3WUJ2tIdMr9Ixd%2BXZeteW1CV1SVbVjWtfBvRdZJ9rOKNq87e5kzLeWsPbZotkHeDgNnZHBcf3TiN3ayxO4aUATTLK1jw6j1RDOMi2zKK%2Bja7W3wwXW2q8ke6tIQryEqFfNQIAblg8ltO4AEpJPztQK5CMXCL1rEAoosSywQswMkbhGGC%2B8ejM7lVeZujqeAADnE9zLFniAV/7wf0BHsv4b0ZJ0CnxcSfNOfKK0fs8hVtaV/OJugJbuDYKBBt6i0IHvHgPAFBOGWifLElVz5qDEJfbg18KzVXvpPEgz9oavxLlRVO%2BAx4NxMP/degCND31AbvGemBgAL3%2BINHuF8B6oJvipPOa1gFYOnkJJgfBwT4I/u2L%2BxCkF/zXhvYB1DwHikgdAgM/DwSIOQaw4e6C776B4Tg/WC9uyiPruIl4ZCpFUKoDvWRL96EELkVA3wKiUHqNvuobhj9sEzxtsInWY9aBGJMRQ6R5iwF8ysQwuhrJe5IMcWg5xrin56yUW/Be3i1q%2BOeP4x6gSLEhNwdY4RjCIl9zUTEzhqgu4qg8J%2BWJeNwS4jUM0bWyAGwfGaLLHGbIOBiDbMQPgcl1KcKwGkWSgMOldNXH0isVBBppSMKYdpLkxktMbLLfSm9jKETMrQJS2yzCSFafMtQVA2wzJlEiRKVTkTWgzrxVKC5MocNUKoAAcpSKklhJhCADNKZI5JqQgEErGFWJIlDxgVhSEk7INROR0srPA6BtIkgAPL8gVgKBWFx9qIoZGmdFFxU4MDyNAisK4i79M5A0oOa46z7M4RKdsB8a6CQ5BwQQrxmiEAgPCIeOlLB8EIKqEFEcKwUsdkxHWxKzZV1IJZXxY8SRPKZG8QgFxRjiErnkJUPA1CQltMQSEtgthiCwJCCwOqWDXAIOgC4wYPAIArCyjgyAPTryXsy1lH52UEBVUqMQxhOXsO4q7XOv5uCHGOBCJU6AyhMHdJ6LCgQkQcAdAwBgFwWBiH4Lkf500anNRCIueEHhtkKX6ZMaQdcc35XBP5HqWEi0ho%2BHFbSHgBEcFjVpQMIYkSsWzYhPQbYgzwgHAmjhrF2LIGzX1AdQ6R0yCHowWQuIZCkBYLIeEy7UCyERFwXgAhiiiAkFIPwChaDLoIGuud867AgFxE4DwDKezUwUAoA%2BA4D5zVxPCXEC6ZBBmXaumQ67SCbpkMujgIB4SkHPYBudpA4CwBgIgFAqAjJ4G2GQCgEA0CofQyAFgFgnC0DmgoUg3lVi5HAxAPgF7SB8vw8QQQshT0NBQ/lJoiK7qMZg6QLADo2DbBo/gf4Yaqx6Bo28D4XxpAyGYxKc0TGz3EA6gp%2BdzA2AgE4NwfgnybACPgPOkwTbwMyEWOaPIwYUhaCVk5fwRVLBaH9prJyQGD2SGkLQVTi7/00ZAy86kag71EaLRkCAuBCCPWcCegM3IUPGDQ7kY9HnVQ7u04IM9F6h5XpvZBhgsg/2kB4DegcRb/C4gHFZGmZWKv%2BH9ABoDIGwMQagxluDiGIBIHEAQAYBByCUGw3F3D9AdZEGIHQUgHQxjGBUz%2BpdK6fOyEi74mhfmqQBY8EF%2BECh0swcy6QXogIs0QE87%2B5dhXB0eHK4O0rl3KuXbm9xhrIgmvQfXbt69QYezrYHE4Hsw7/BbNoP4AcQYBzftyzILbBW6AKSLcW4t9A6sbtkNt17x2nDeYe8j5rO352icYpMEAQYgA)
; loop body
%rep T
%if FUSE
or edi, [rdi] ; static buffers are in the low 32 bits of address space, in non-PIE
%else
mov eax, [rdi]
or edi, eax
%endif
%endrep
%rep T
%if FUSE
or esi, [rsi]
%else
mov eax, [rsi]
or esi, eax
%endif
%endrep
看着uops_executed.thread
(未融合域)每个周期(或每秒)perf
为我们计算),我们可以看到不依赖于单独负载与折叠负载的吞吐量数字。
使用较小的 T (T=30),可以利用所有 ILP,无论有或没有微融合,我们每个时钟都能获得约 0.67 uop。 (我忽略了来自 dec/jnz 的每个循环迭代 1 个额外 uop 的小偏差。与我们看到的微融合 uop 仅使用 1 个 RS 条目所看到的效果相比,它可以忽略不计)
记住负载+or
是 2 uop,并且我们有 2 个飞行中的 dep 链,所以这是 4/6,因为or edi, [rdi]
有6个周期的延迟。 (不是 5,这令人惊讶,见下文。)
在 T=60 时,对于 FUSE=0,我们仍然每个时钟执行约 0.66 个未融合的微指令,对于 FUSE=1,每个时钟执行约 0.64 个未融合微指令。我们仍然可以找到基本上所有的 ILP,但它才刚刚开始下降,因为两个 dep 链的长度为 120 uops(相对于 RS 大小为 97)。
在 T=120 时,对于 FUSE=0,每个时钟有 0.45 个未融合的微指令,对于 FUSE=1,每个时钟有 0.44 个未融合的微指令。我们肯定已经过了膝盖了,但仍然发现someILP 的。
如果微融合 uop 仅占用 1 个 RS 条目,则 FUSE=1 T=120 的速度应与 FUSE=0 T=60 大致相同,但事实并非如此。相反,FUSE=0 或 1 在任何 T 上几乎没有区别。(包括较大的 T=200:FUSE=0:0.395 uops/时钟,FUSE=1:0.391 uops/时钟)。我们必须去very在我们开始 1 个深度链在飞行中的时间之前,T 大 T 完全控制了 2 个在飞行中的时间,并下降到 0.33 uops/时钟 (2/6)。
奇怪的是:融合与非融合的吞吐量差异如此之小,但仍然可测量,并且单独的mov
加载速度更快。
其他奇怪之处:总计uops_executed.thread
is slightly在任何给定 T 下,FUSE=0 的值较低。例如 T=60 时为 2,418,826,591 与 2,419,020,155。这种差异可重复至 2.4G 中的 +- 60k,足够精确。 FUSE=1 的总时钟周期较慢,但大部分差异来自于每个时钟的微指令数较低,而不是更多的微指令数。
简单的寻址模式如[rdi]
假设只有 4 个周期延迟,因此负载 + ALU 应该只有 5 个周期。但我测量了 6 个周期的加载使用延迟or rdi, [rdi]
,或者使用单独的 MOV 加载,或者使用任何其他 ALU 指令,我永远无法将加载部分设为 4c。
复杂的寻址模式如[rdi + rbx + 2064]
当 dep 链中有 ALU 指令时,具有相同的延迟,因此看来 Intel 的 4c 延迟适用于简单寻址模式only当一个负载转发到另一个负载的基址寄存器时适用(最多 +0..2047 位移且无索引)。
指针追逐很常见,因此这是一种有用的优化,但我们需要将其视为特殊的负载-负载转发快速路径,而不是更快准备好供 ALU 指令使用的一般数据。
P6 系列不同:RS 条目保存融合域 uop。
@哈迪发现2002 年英特尔专利 https://patents.google.com/patent/US20040034757A1/en,其中图 12 显示了融合域中的 RS。
在 Conroe(第一代 Core 2 Duo,E6600)上进行的实验测试表明,对于 T=50,FUSE=0 和 FUSE=1 之间存在很大差异。 (RS大小为32个条目 https://www.realworldtech.com/merom/6/).
-
T=50 FUSE=1:2.346G周期总时间(0.44IPC)
-
T=50 FUSE=0:3.272G 周期的总时间(0.62IPC = 0.31 负载+OR 每个时钟)。 (perf
/ ocperf.py
没有活动uops_executed
在 Nehalem 之前的 uarches 上,我没有oprofile
安装在该机器上。)
-
T=24 FUSE=0 和 FUSE=1 之间的差异可以忽略不计,大约为 0.47 IPC 与 0.9 IPC(每个时钟约 0.45 负载+OR)。
T=24 仍然是循环中超过 96 字节的代码,对于 Core 2 的 64 字节(预解码)循环缓冲区来说太大,因此由于适合循环缓冲区,它的速度并没有更快。如果没有 uop 缓存,我们必须担心前端,但我认为我们很好,因为我专门使用 2 字节单 uop 指令,这些指令应该可以轻松地以每个时钟 4 个融合域 uop 进行解码。