了解lfence对具有两个长依赖链的循环的影响,以增加长度

问题描述 投票:13回答:2

我正在玩this answer中的代码,略微修改它:

BITS 64

GLOBAL _start

SECTION .text

_start:
 mov ecx, 1000000

.loop:

 ;T is a symbol defined with the CLI (-DT=...)

 TIMES T imul eax, eax
 lfence
 TIMES T imul edx, edx


 dec ecx
jnz .loop

 mov eax, 60           ;sys_exit
 xor edi, edi
 syscall

如果没有lfence,我得到的结果与该答案中的静态分析一致。

当我引入单个lfence时,我希望CPU与下一个(第k + 1)次迭代的imul edx, edx序列并行执行第k次迭代的imul eax, eax序列。 像这样的东西(称为Qazxswpoi序列,D称为imul eax, eax序列):

imul edx, edx

采用或多或少相同数量的周期,但是对于一个不成对的并行执行。

当我测量循环次数时,对于原始和修改版本,| | A | D A | D A | D A | ... | D A | D | V time taskset -c 2 ocperf.py stat -r 5 -e cycles:u '-x ' ./main-$T在下面的范围内我得到

T

T Cycles:u Cycles:u Delta lfence no lfence 10 42047564 30039060 12008504 15 58561018 45058832 13502186 20 75096403 60078056 15018347 25 91397069 75116661 16280408 30 108032041 90103844 17928197 35 124663013 105155678 19507335 40 140145764 120146110 19999654 45 156721111 135158434 21562677 50 172001996 150181473 21820523 55 191229173 165196260 26032913 60 221881438 180170249 41711189 65 250983063 195306576 55676487 70 281102683 210255704 70846979 75 312319626 225314892 87004734 80 339836648 240320162 99516486 85 372344426 255358484 116985942 90 401630332 270320076 131310256 95 431465386 285955731 145509655 100 460786274 305050719 155735555

如何解释Plotted data of above的价值? 我原本期望它们与Cycles:u lfence类似,因为单个Cycles:u no lfence应该只阻止第一次迭代并行执行两个块。 我不认为这是由于lfence开销,因为我认为所有lfences应该是恒定的。

在处理代码的静态分析时,我想解决我的形式问题。


T

performance assembly x86 cpu-architecture perf
2个回答
4
投票

我将对两个代码(有和没有Supporting repository with source files)的T = 1的情况进行分析。然后,您可以将其扩展为T的其他值。您可以参考英特尔优化手册的图2.4以获得可视化。

因为只有一个容易预测的分支,所以前端只会在后端停滞时停止。 Haswell中的前端是4个宽,这意味着可以从IDQ(指令解码队列,它只是一个保持有序融合域uop的队列,也称为uop队列)发出最多4个融合uop。预定站(RS)的调度程序。每个lfence被解码成一个不能融合的uop。 imuldec ecx指令在前端使用macrofused到单个uop。微观和宏观融合之间的区别之一是,当调度程序将宏迭代的uop(未被微量使用)分派给它所分配的执行单元时,它将被分派为单个uop。相反,微博的uop需要分成其组成的uop,每个uop必须单独分派到执行单元。 (然而,拆分微型uops发生在RS的入口处,而不是发送,请参阅@Peter的答案中的脚注2)。 jnz .loop解码为6 uops。识别微缩仅在后端很重要,在这种情况下,循环中没有微缩。

由于循环分支很容易预测,并且由于迭代次数相对较大,我们可以假设分配器始终能够在每个循环中分配4个uop而不会影响精度。换句话说,调度程序每个周期将接收4个uop。由于没有微观,每个uop将作为单个uop发送。

lfence只能由Slow Int执行单元执行(参见图2.4)。这意味着执行imul uops的唯一选择是将它们分配到端口1.在Haswell中,Slow Int很好地流水线化,因此每个周期可以调度一个imul。但是,对于任何需要的指令,乘法结果需要三个周期(回写阶段是从管道的调度阶段开始的第三个周期)。因此,对于每个依赖链,每3个循环最多可以发送一个imul

因为预测采用imul,所以可以执行它的唯一执行单元是端口6上的主分支。

因此,在任何给定周期,只要RS有空间,它就会收到4个uop。但是什么样的uops?让我们检查没有lfence的循环:

dec/jnz

有两种可能性:

  • 来自相同迭代的两个imul eax, eax imul edx, edx dec ecx/jnz .loop (macrofused) s,来自相邻迭代的一个imul,以及来自这两个迭代之一的一个imul
  • 来自一次迭代的一个dec/jnz,来自下一次迭代的两个dec/jnzs,以及来自同一次迭代的一个imul

因此,在任何周期的开始,RS将从每个链接收至少一个dec/jnz和至少一个dec/jnz。同时,在同一个循环中以及RS中已存在的那些uops中,调度程序将执行以下两个操作之一:

  • 将最古老的imul发送到6号港口,并派遣准备进入港口1的最古老的dec/jnz。这总共是2次。
  • 因为Slow Int具有3个周期的延迟,但是只有两个链,对于3个周期的每个周期,RS中的imul都不准备执行。但是,RS中始终至少有一个imul。所以调度程序可以调度它。这总共是1次。

现在我们可以在任何给定周期N的末尾计算RS,XN中的预期uop数量N:

XN = XN-1 +(在周期N开始时在RS中分配的uop数) - (将在周期N的开始处调度的预期uop数) = XN-1 + 4 - ((0 + 1)* 1/3 +(1 + 1)* 2/3) = XN-1 + 12/3 - 5/3 对于所有N> 0,= XN-1 + 7/3

重现的初始条件是X0 = 4.这是一个简单的重复,可以通过展开XN-1来解决。

对于所有N> = 0,XN = 4 + 2.3 * N.

Haswell的RS有60个参赛作品。我们可以确定预期RS变满的第一个周期:

60 = 4 + 7/3 * N. N = 56 / 2.3 = 24.3

因此,在周期24.3结束时,预计RS将满。这意味着在周期25.3的开始,RS将无法接收任何新的uop。现在,正在考虑的迭代次数决定了您应该如何进行分析。由于依赖链需要至少3 * I个循环才能执行,因此需要大约8.1次迭代才能达到循环24.3。因此,如果迭代次数大于8.1,这就是这里的情况,您需要分析循环24.3之后发生的情况。

调度程序在每个周期以以下速率发送指令(如上所述):

dec/jnz

但是除非至少有4个可用条目,否则分配器不会在RS中分配任何uop。否则,它不会浪费能力以低于最佳的吞吐量发布uop。但是,只有在每个第4个周期的开始,RS中至少有4个空闲条目。因此,从周期24.3开始,预计分配器将在每4个周期中停止3次。

对正在分析的代码的另一个重要观察是,永远不会发生超过4个uop可以被调度,这意味着每个周期离开执行单元的平均uop数不大于4个。最多4个uop可以从ReOrder Buffer(ROB)退役。这意味着ROB永远不会处于关键路径上。换句话说,性能由调度吞吐量决定。

我们现在可以相当容易地计算IPC(每个周期的指令)。 ROB条目看起来像这样:

1
2
2
1
2
2
1
2
.
.

右边的列显示了可以退出指令的周期。退休按顺序发生,并受关键路径延迟的限制。这里每个依赖链具有相同的路径长度,因此两者都构成长度为3个周期的两个相等的关键路径。因此,每3个周期,4个指令可以退役。因此IPC为4/3 = 1.3,CPI为3/4 = 0.75。这远小于4的理论最佳IPC(即使不考虑微观和宏观融合)。由于退休是按顺序发生的,退休行为将是相同的。

我们可以使用imul eax, eax - N imul edx, edx - N + 1 dec ecx/jnz .loop - M imul eax, eax - N + 3 imul edx, edx - N + 4 dec ecx/jnz .loop - M + 1 和IACA检查我们的分析。我会讨论perf。我有一个Haswell CPU。

perf

有100万次迭代,每次约需3个周期。每次迭代包含4条指令,IPC为1.33.perf stat -r 10 -e cycles:u,instructions:u,cpu/event=0xA2,umask=0x10,name=RESOURCE_STALLS.ROB/u,cpu/event=0x0E,umask=0x1,cmask=1,inv=1,name=UOPS_ISSUED.ANY/u,cpu/event=0xA2,umask=0x4,name=RESOURCE_STALLS.RS/u ./main-1-nolfence Performance counter stats for './main-1-nolfence' (10 runs): 30,01,556 cycles:u ( +- 0.00% ) 40,00,005 instructions:u # 1.33 insns per cycle ( +- 0.00% ) 0 RESOURCE_STALLS.ROB 23,42,246 UOPS_ISSUED.ANY ( +- 0.26% ) 22,49,892 RESOURCE_STALLS.RS ( +- 0.00% ) 0.001061681 seconds time elapsed ( +- 0.48% ) 显示由于完整的ROB而导致分配器停止的周期数。这当然永远不会发生。 RESOURCE_STALLS.ROB可用于计算发给RS的uop数和分配器停止的周期数(无特殊原因)。第一个是直截了当的(未在UOPS_ISSUED.ANY输出中显示); 100万* 3 = 300万+小噪音。后者更有趣。它表明,由于完整的RS,分配器停滞的时间总共有73%,这与我们的分析相符。 perf计算由于完整RS而导致分配器停止的周期数。这接近于RESOURCE_STALLS.RS,因为分配器不会因任何其他原因而停止(尽管由于某种原因差异可能与迭代次数成比例,我将不得不看到T> 1的结果)。

没有UOPS_ISSUED.ANY的代码分析可以扩展,以确定如果在两个lfences之间添加lfence会发生什么。让我们先查看imul结果(不幸的是IACA不支持perf):

lfence

观察到循环次数增加了大约1000万次,或每次迭代10次循环。循环次数并没有告诉我们多少。退休指令的数量增加了一百万,这是预期的。我们已经知道perf stat -r 10 -e cycles:u,instructions:u,cpu/event=0xA2,umask=0x10,name=RESOURCE_STALLS.ROB/u,cpu/event=0x0E,umask=0x1,cmask=1,inv=1,name=UOPS_ISSUED.ANY/u,cpu/event=0xA2,umask=0x4,name=RESOURCE_STALLS.RS/u ./main-1-lfence Performance counter stats for './main-1-lfence' (10 runs): 1,32,55,451 cycles:u ( +- 0.01% ) 50,00,007 instructions:u # 0.38 insns per cycle ( +- 0.00% ) 0 RESOURCE_STALLS.ROB 1,03,84,640 UOPS_ISSUED.ANY ( +- 0.04% ) 0 RESOURCE_STALLS.RS 0.004163500 seconds time elapsed ( +- 0.41% ) 不会更快地完成指令,所以lfence不应该改变。 RESOURCE_STALLS.ROBUOPS_ISSUED.ANY特别有趣。在此输出中,RESOURCE_STALLS.RS计算周期,而不是uops。 uop的数量也可以计算(使用UOPS_ISSUED.ANY而不是cpu/event=0x0E,umask=0x1,name=UOPS_ISSUED.ANY/u)并且每次迭代增加6 uop(无融合)。这意味着放置在两个cpu/event=0x0E,umask=0x1,cmask=1,inv=1,name=UOPS_ISSUED.ANY/us之间的lfence被解码为6个uop。一百万美元的问题现在是这些微博所做的以及它们如何在管道中移动。

imul为零。那是什么意思?这表明分配器,当它在IDQ中看到RESOURCE_STALLS.RS时,它停止分配,直到ROB中的所有当前uop退出。换句话说,在lfence退休之前,分配器不会在RS中经过lfence分配条目。由于循环体仅包含3个其他uop,因此60条入口RS将永远不会满。事实上,它总是几乎是空的。

实际上,IDQ不是一个简单的队列。它由多个可并行操作的硬件结构组成。 lfence所需的uops数量取决于IDQ的确切设计。分配器也由许多不同的硬件结构组成,当它看到在IDQ的任何结构的前面都有一个lfence uops时,它暂停从该结构的分配,直到ROB为空。因此,不同的uops具有不同的硬件结构。

lfence表明,每次迭代,分配器不会发出任何uop大约9-10个周期。这里发生了什么?好吧,UOPS_ISSUED.ANY的一个用途是它可以告诉我们退出指令和分配下一条指令需要多长时间。以下汇编代码可用于执行此操作:

lfence

性能事件计数器不适用于TIMES T lfence 的小值。对于足够大的T,并通过测量T,我们可以确定退出每个UOPS_ISSUED.ANY需要大约4个周期。这是因为lfence每5个周期增加约4次。因此,在每4个周期后,分配器发出另一个UOPS_ISSUED.ANY(它不会停止),然后等待另外4个周期,依此类推。也就是说,根据指令,产生结果的指令可能需要1或几个周期才能退出。 IACA总是假设退出指令需要5个周期。

我们的循环如下所示:

lfence

imul eax, eax lfence imul edx, edx dec ecx jnz .loop 边界的任何周期,ROB将包含从ROB顶部开始的以下指令(最早的指令):

lfence

其中N表示调度相应指令的循环数。将要完成的最后一条指令(到达回写阶段)是imul edx, edx - N dec ecx/jnz .loop - N imul eax, eax - N+1 。这发生在第N + 4周期。分配器停顿周期计数将在周期N + 1,N + 2,N + 3和N + 4期间递增。然而,在imul eax, eax退休之前,它将再进行约5个循环。此外,在它退出之后,分配器需要从IDQ中清除imul eax, eax uop并分配下一组指令,然后才能在下一个周期中调度它们。 lfence输出告诉我们每次迭代需要大约13个周期,并且分配器在这13个周期中的10个中停止(因为perf)。

问题中的图表仅显示最多T = 100的周期数。然而,此时还有另一个(最终)膝盖。因此,最好将周期绘制到T = 120以查看完整模式。


7
投票

我认为你的测量准确,而且解释是微体系结构,而不是任何测量误差。


我认为你的中到低T的结果支持这样的结论:lfence阻止前端甚至发出超过lfence,直到所有先前的指令退出,而不是已经发出两条链中的所有uop并等待lfence翻转切换并让每个链的倍数开始在交替的周期上发送。

(对于Skylake的3c延迟/ 1c吞吐量乘数,port1将获得edx,eax,empty,edx,eax,empty,...如果lfence没有阻塞前端,并且开销不会随着T而扩展。 )

当只有来自第一个链的uops在调度程序中时,你正在失去lfence吞吐量,因为前端还没有通过imul和loop分支进行咀嚼。并且对于窗口末端的相同循环次数,当管道大部分被排空并且仅剩下来自第二链的uops时。


开销增量看起来是线性的,约为T = 60。我没有运行数字,但是那里的斜率看起来合理的imul edx,edx时钟发出第一个链与3c延迟执行瓶颈。即delta增长速度可能是总无噪声周期的1/12。

所以(考虑到我在下面测量的T * 0.25开销),T <60:

lfence

@Margaret报告说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 T/4更合适,但我预计在开始和结束时都会有T / 4,总共有2T / 4的斜率。


在大约T = 60之后,delta增长得更快(但仍然是线性的),斜率大约等于总无噪声周期,因此每T大约3c。我认为在那时,调度程序(Reservation Station)的大小是限制无序窗口。你可能在Haswell或Sandybridge / IvyBridge上测试过,(2*T / 4 .Skylake是97条目。

RS跟踪未执行的uops。每个RS条目保存1个未融合域uop,等待其输入准备就绪,以及它的执行端口,然后才能发送并离开RS1。

which have a 60-entry or 54-entry scheduler respectively之后,前端以每时钟4个发出,而后端以每3个时钟1个为单位执行,在~15个周期内发出60个uop,在此期间,仅执行了lfence链中的5个imul指令。 (这里没有加载或存储微融合,因此来自前端的每个融合域uop在RS2中仍然只有1个未融合域uop。)

对于大T,RS快速填满,此时前端只能以后端的速度进行。 (对于小T,我们在发生之前击中了下一个迭代的edx,这就是前端的停滞)。当T> RS_size时,后端无法看到来自lfence imul链的任何uop,直到通过eax链的足够的后端进度已经在RS中腾出空间。此时,每个链中的一个edx可以每3个周期发送一次,而不仅仅是第一个或第二个链。

请记住,从第一部分开始,在imul之后仅执行第一个链=时间恰好在lfence执行第二个链之前的时间。这也适用于此。

即使没有lfence,我们也会得到一些这样的效果,对于T> RS_size,但是在长链的两边都有重叠的机会。 ROB的大小至少是RS的两倍,因此当没有被lfence停滞时,无序窗口应该能够使两个链保持不变,即使T稍微大于调度器容量。 (请记住,uops一执行就会离开RS。我不确定这是否意味着他们必须完成执行并转发结果,或者只是开始执行,但这对于简短的ALU指令来说这是一个小小的区别。他们已经完成了,只有ROB按照程序顺序保留它们直到它们退休。)

ROB和寄存器文件不应该在这种假设情况下或在您的实际情况下限制无序窗口大小(lfence)。它们应该都很大。


在英特尔的搜索中,阻止前端是http://blog.stuffedcow.net/2013/05/measuring-rob-capacity/的实现细节。该手册仅表示后续指令无法执行。只要没有人被分派到执行单元,该措辞将允许前端将它们全部发布/重命名为调度程序(Reservation Station)和ROB,而lfence仍在等待。

因此,较弱的lfence可能在T = RS_size之前具有平坦的开销,然后是与T> 60时相同的斜率。 (并且开销的不变部分可能会更低。)

请注意,在lfence之后推测执行条件/间接分支的保证适用于执行,而不是(据我所知)代码获取。仅仅触发代码获取(AFAIK)对于Spectre或Meltdown攻击并非有用。可能是一个定时侧通道来检测它的解码方式可能会告诉你有关所提取代码的信息......

当相关的MSR启用时,我认为AMD的LFENCE至少与实际的AMD CPU一样强。 (lfence)。


额外的Is LFENCE serializing on AMD processors?开销:

你的结果很有意思,但是我并不感到惊讶,因为lfence本身(对于小T)以及与T一起扩展的组件有显着的持续开销。

请记住,lfence不允许以后的指令在早期指令退役之前启动。这可能至少比他们的结果准备好绕过前往其他执行单元(即正常等待时间)的时候至少几个周期/流水线阶段。

因此对于小T来说,通过要求结果不仅准备就绪,而且还要写回寄存器文件,您必须在链中添加额外的延迟,这一点非常重要。

lfence可能需要一个额外的周期才能允许发出/重命名阶段在检测到它之前的最后一条指令退出后再次开始运行。问题/重命名过程需要多个阶段(周期),并且可能在此开始时阻止阻塞,而不是在将uops添加到核心的OoO部分之前的最后一步。

根据Agner Fog的测试,即使背对背的lfence本身在SnB系列上也有4个周期的吞吐量。 lfence 2融合域uops(没有未融合),但在Skylake我测量它在6融合域(仍然没有未融合),如果我只有1 Agner Fog reports。但随着更多的lfence背靠背,它的uops更少!每个lfence下降到~2 uops,背靠背很多,这就是Agner测量的方式。

lfence / lfence / dec(一个没有工作的紧密循环)在SKL上每10个周期运行1次迭代,这样可以让我们了解jnz即使没有前端和RS充分的瓶颈。

仅使用一个dep链测量lfence开销,OoO exec无关紧要:

lfence

如果没有.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 ,则预期每次运行30.0c。使用lfence,每次运行速度约为39.3c,因此lfence有效地将〜9.3c的“额外延迟”添加到关键路径段。 (还有6个额外的融合域uops)。

在imul链之后的lfence,就在循环分支之前,它稍慢。但是整个周期并不慢,所以这表明前端在lfence允许执行恢复之后在单个问题组中发出循环分支+和imul。既然如此,IDK为什么会慢一些。这不是分支未命中。


获得您期望的行为:

以程序顺序交织链,就像@BeeOnRope在评论中建议的那样,不需要无序执行来利用ILP,所以它非常简单:

lfence

你可以把一对短的.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里让OoO执行官有一个轻松的时间。


脚注1:前端/ RS / ROB如何相互作用

我的心理模型是前端的问题/重命名/分配阶段同时为RS和ROB添加新的uop。

Uops在执行后离开RS,但留在ROB中直到有序退役。 ROB可能很大,因为它从来没有被无序扫描以找到第一个就绪的uop,只是按顺序扫描以检查最早的uop是否已经完成执行并因此准备退出。

(我假设ROB在物理上是一个带有开始/结束索引的循环缓冲区,而不是每个周期实际上将uop复制到右侧的队列。但是只要将其视为具有固定最大大小的队列/列表,其中前端在前面添加uops,并且退出逻辑从完成后退出/提交uop,只要它们被完全执行,直到每个周期的每个超线程退休限制,这通常不是瓶颈.Skylake确实增加了它更好超线程,每个逻辑线程可能每个时钟8个。也许退出也意味着释放有助于HT的物理寄存器,因为当两个线程都处于活动状态时,ROB本身是静态分区的。这就是为什么退出限制是每个逻辑线程的原因。)

在前端处理的%repnopxor eax,eax等uops(在任何端口上不需要任何执行单元)仅在已执行状态下添加到ROB。 (一个ROB条目可能有一点标志着它准备退休而不是等待执行完成。这就是我正在谈论的状态。对于需要执行端口的uops,我假设ROB位被设置通过来自执行单元的lfence。并且相同的完成端口信号释放其RS条目。)

Uops留在ROB从发行到退休。

从问题到执行,Uops都会留在RS中。 RS可以在少数情况下重放uop,例如completion port,或者如果它是在预期负载数据到达时发送的,但实际上并没有。 (缓存未命中或其他冲突,如for the other half of a cache-line-split load。)或者当加载端口推测它可以在开始TLB查找之前绕过AGU以缩短具有小偏移的指针追踪延迟 - Weird performance effects from nearby dependent stores in a pointer-chasing loop on IvyBridge. Adding an extra load speeds it up?

所以我们知道RS在发送时无法删除uop,因为它可能需要重放。 (甚至可以发生消耗负载数据的非负载uops。)但是任何需要重放的推测都是短程的,而不是通过uop链,所以一旦结果出现在执行单元的另一端,uop就可以了从RS中删除。这可能是完成端口所做的一部分,同时将结果放在旁路转发网络上。


Footnote 2: How many RS entries does a micro-fused uop take?

TL:DR:P6-家族:RS融合,SnB-家族:RS未融合。

微融合uop被发布到Sandybridge系列中的两个独立的RS条目,但只有1个ROB条目。 (假设在发布之前没有进行非层压,请参阅第2.3.5节有关HSW或第2.4.2.4节有关英特尔优化手册的SnB和Is there a penalty when base+offset is in a different page than the base?.Sandybridge-family的更紧凑的uop格式不能代表索引的寻址模式。在所有情况下都是ROB。)

负载可以独立调度,在ALU uop准备就绪的另一个操作数之前。 (或者对于微型融合存储,存储地址或存储数据uop可以在其输入准备就绪时进行调度,而无需等待两者。)

我使用问题中的双脱链方法在Skylake(RS大小= 97)上进行实验测试,使用微融合Micro fusion and addressing modesor edi, [rdi] + mov,以及or中的另一个dep链。 (rsi

Full test code, NASM syntax on Godbolt

查看; 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为我们计算的每秒),我们可以看到不依赖于单独与折叠负载的吞吐量数量。

对于小T(T = 30),可以利用所有ILP,并且在有或没有微融合的情况下,我们得到每时钟约0.67微秒。 (我忽略了来自dec / jnz的每次循环迭代的1次额外uop的小偏差。与我们看到的微融合uop仅使用1个RS入口的效果相比,它可以忽略不计)

请记住,load + perf是2 uops,我们在飞行中有2个dep链,所以这是4/6,因为or有6个周期延迟。 (不是5,这很令人惊讶,见下文。)

在T = 60时,对于FUSE = 0,我们仍然在每个时钟执行大约0.66个未融合的uop,对于FUSE = 1,我们仍然有0.64。我们仍然可以找到基本上所有的ILP,但它刚刚开始下降,因为两个dep链长120微秒(相对于RS大小97)。

在T = 120时,对于FUSE = 0,我们每个时钟有0.45个未融合的uop,对于FUSE = 1,我们有0.44个未熔丝的uop。我们肯定已经过了膝盖,但仍然找到了一些ILP。

如果微融合uop只接受1个RS输入,则FUSE = 1 T = 120应该与FUSE = 0 T = 60的速度大致相同,但事实并非如此。相反,FUSE = 0或1在任何T处几乎没有差别(包括较大的那些,如T = 200:FUSE = 0:0.395 uops / clock,FUSE = 1:0.391 uops / clock)。我们必须在我们开始之前使用非常大的T,在飞行中使用1个dep-chain以完全控制飞行中的2个时间,并且下降到0.33 uops / clock(2/6)。

奇怪:我们在融合与未融合的吞吐量方面存在如此小的但仍然可测量的差异,单独的or edi, [rdi]负载更快。

其他奇怪之处:在任何给定的T,FUSE = 0时总的mov略低。如2,418,826,591与T = 60的2,419,020,155相比。这种差异可以重复到2.4G的+ - 60k,足够精确。 FUSE = 1在总时钟周期中较慢,但大部分差异来自每个时钟的较低uop,而不是来自更多uops。

uops_executed.thread这样的简单寻址模式应该只有4个周期延迟,所以load + ALU应该只有5个周期。但是我测量了[rdi]的负载使用延迟的6个周期延迟,或者使用单独的MOV负载,或者使用任何其他ALU指令,我永远无法将负载部分设置为4c。

像depz链中的ALU指令一样,像or rdi, [rdi]这样的复杂寻址模式具有相同的延迟,所以看起来英特尔的简单寻址模式的4c延迟仅适用于负载转发到另一个负载的基址寄存器时(最多为+ 0..2047位移且没有索引)。

指针追逐很常见,这是一个有用的优化,但我们需要将其视为特殊的负载转发快速路径,而不是ALU指令使用的通用数据。


P6系列是不同的:RS条目包含融合域uop。

@Hadi找到了[rdi + rbx + 2064],其中图12显示了融合域中的RS。

对Conroe(第一代Core2Duo,E6600)的实验测试表明,对于T = 50,FUSE = 0和FUSE = 1之间存在很大差异。 (an Intel patent from 2002)。

  • T = 50 FUSE = 1:总时间为2.346G周期(0.44IPC)
  • T = 50 FUSE = 0:总时间为3.272G周期(0.62IPC = 0.31负载+每时钟OR)。 (The RS size is 32 entries / perf在Nehalem左右之前没有关于ocperf.py的事件,我没有在那台机器上安装uops_executed。)
  • T = 24,FUSE = 0和FUSE = 1之间的差异可以忽略不计,大约0.47 IPC vs 0.9 IPC(~0.45负载+每个时钟OR)。

T = 24仍然在循环中超过96个字节的代码,对于Core 2的64字节(预解码)循环缓冲区而言太大了,因此它不会因为适合循环缓冲区而更快。如果没有uop-cache,我们必须担心前端,但我认为我们很好,因为我只使用2字节单uop指令,每个时钟应该可以轻松解码4个融合域uop。

© www.soinside.com 2019 - 2024. All rights reserved.