当前位置: 首页 > 知识库问答 >
问题:

来自附近依赖商店的奇怪性能影响,在IvyBridge上的指针追逐循环中。添加额外的负载会加快速度?

严修谨
2023-03-14

首先,我在IvyBridge上有下面的设置,我将在注释的位置插入测量有效载荷代码。< code>buf的前8个字节存储< code>buf本身的地址,我用它来创建循环携带的依赖关系:

section .bss
align   64
buf:    resb    64

section .text
global _start
_start:
    mov rcx,         1000000000
    mov qword [buf], buf
    mov rax,         buf
loop:
    ; I will insert payload here
    ; as is described below 

    dec rcx
    jne loop

    xor rdi,    rdi
    mov rax,    60
    syscall

我插入有效载荷位置:

mov qword [rax+8],  8
mov rax,            [rax]

perf显示循环为5.4c/iter。这有点容易理解,因为L1d延迟是4个周期。

我颠倒这两个指令的顺序:

mov rax,            [rax]
mov qword [rax+8],  8

结果突然变成9c/iter。我不明白为什么。因为下一次迭代的第一条指令不依赖于当前迭代的第二条指令,所以这个设置不应该与第一种情况不同。

我还使用IACA工具静态分析了这两个案例,但该工具不可靠,因为它预测两个案例的结果相同,均为5.71c/iter,这与实验结果相矛盾。

然后,我在案例 2 中插入一个不相关的 mov 指令:

mov rax,            [rax]
mov qword [rax+8],  8
mov rbx,            [rax+16] 

现在结果变为 6.8c/iter。但是,插入的不相关的mov如何将速度从9c / iter提高到6.8c / iter?

IACA工具预测错误的结果,如前一种情况,它显示5.24c/iter。

我现在完全糊涂了,如何理解上述结果?

在情况 1 和 2 中,有一个地址 rax 8。如果将 rax 8 更改为 rax 16 或 rax 24,则情况 1 和 2 的结果保持不变。但是当它被更改为 rax 32 时,会发生一些令人惊讶的事情:案例 1 变成 5.3c/iter,案例 2 突然变成 4.2c/iter。

$ perf stat -ecycles,ld_blocks_partial.address_alias,int_misc.recovery_cycles,machine_clears.count,uops_executed.stall_cycles,resource_stalls.any ./a.out

[rax 8]的案例1:

 5,429,070,287      cycles                                                        (66.53%)
         6,941      ld_blocks_partial.address_alias                                     (66.75%)
       426,528      int_misc.recovery_cycles                                      (66.83%)
        17,117      machine_clears.count                                          (66.84%)
 2,182,476,446      uops_executed.stall_cycles                                     (66.63%)
 4,386,210,668      resource_stalls.any                                           (66.41%)

[rax 8]的情况2:

 9,018,343,290      cycles                                                        (66.59%)
         8,266      ld_blocks_partial.address_alias                                     (66.73%)
       377,824      int_misc.recovery_cycles                                      (66.76%)
        10,159      machine_clears.count                                          (66.76%)
 7,010,861,225      uops_executed.stall_cycles                                     (66.65%)
 7,993,995,420      resource_stalls.any                                           (66.51%)

[饶舌 8] 的情况 3:

 6,810,946,768      cycles                                                        (66.69%)
         1,641      ld_blocks_partial.address_alias                                     (66.73%)
       223,062      int_misc.recovery_cycles                                      (66.73%)
         7,349      machine_clears.count                                          (66.74%)
 3,618,236,557      uops_executed.stall_cycles                                     (66.58%)
 5,777,653,144      resource_stalls.any                                           (66.53%)

< code>[rax 32]的情况2:

 4,202,233,246      cycles                                                        (66.68%)
         2,969      ld_blocks_partial.address_alias                                     (66.68%)
       149,308      int_misc.recovery_cycles                                      (66.68%)
         4,522      machine_clears.count                                          (66.68%)
 1,202,497,606      uops_executed.stall_cycles                                     (66.64%)
 3,179,044,737      resource_stalls.any                                           (66.64%)

共有1个答案

杨晓博
2023-03-14

TL;DR:对于这三种情况,当同时执行加载和存储时,会导致几个周期的损失。在所有三种情况下,加载延迟都在关键路径上,但是不同情况下的损失不同。由于额外的负载,情况3比情况1大约高一个周期。

分析方法 1:使用停滞性能事件

我可以在IvB和SnB上重现你的三个病例的结果。我得到的数字和你的数字相差不到2%。执行情况1、2和4的单次迭代所需的周期数分别为5.4、8.9和6.6。

先说前端。<代码>迷幻药。CYCLES_4_UOPS和< code>LSD。CYCLES _ 3 _ UOP 性能事件表明,基本上所有的UOP都是从LSD发出的。此外,这些事件与< code>LSD一起。CYCLES_ACTIVE显示在LSD未停止的每个周期中,情况1和情况2发出3个微操作,情况3发出4个微操作。换句话说,正如预期的那样,在单个周期中,每个迭代的微操作在同一个组中一起发出。

在以下所有关系中,“=~"符号意味着差异在2%以内。我将从以下经验观察开始:

< code>UOPS_ISSUED。STALL_CYCLES LSD。CYCLES_ACTIVE =~ 周期数

请注意,需要按照此处所述调整 SnB 上的 LSD 事件计数。

我们还有以下关系:

案例1: UOPS_ISSUED。STALL _ CYCLES = ~ < code > RESOURCE _ stals。任何 =~ 4.4c/iter
情况2: UOPS_ISSUED。STALL _ CYCLES = ~ < code > RESOURCE _ stals。任何 =~ 7.9c/iter
情况3: UOPS_ISSUED。STALL _ CYCLES = ~ < code > RESOURCE _ stals。任何 =~ 5.6c/iter

这意味着问题停顿的原因是因为后端中的一个或多个所需资源不可用。因此,我们可以自信地从考虑中消除整个前端。在情况1和2中,该资源是RS。在情况3中,由于RS造成的停顿约占所有资源停顿的20%1

现在让我们关注案例1。共有4个未融合的域uop:1个load uop、1个STA、1个STD和1个dec/jne。载荷和STA uop取决于先前的载荷uop。每当LSD发出一组uop时,STD和跳转uop都可以在下一个周期中发送,因此下一个循环不会导致执行暂停事件。但是,可以调度加载和STA uop的最早点是在写回加载结果的相同周期中。CYCLES_NO_EXECUTESTALLS_LDM_PENDINGLSD。CYCLES_3_OUPS显示LSD等待,直到RS中至少有4个空闲条目,然后才发出一组uop,构成完整迭代。在下一个周期中,将调度其中两个uop,从而释放2个RS条目2。另一个则必须等待其所依赖的负载完成。很可能加载是按程序顺序完成的。因此,LSD一直等到STA和加载尚未执行的最旧迭代的uop离开RS。因此,uops_ISSUED。STALL_CYCLES1=~平均加载延迟3。我们可以得出结论,情况1中的平均加载延迟为5.4c。大多数情况都适用于案例2,但有一点不同,我稍后会解释。

由于每次迭代中的uops都形成了一个依赖链,我们也有:

循环=~平均加载延迟。

因此:

循环=~UOPS_ISSUED.STALL_CYCLES1=~平均加载延迟。

在案例1中,平均加载延迟为5.4c。我们知道L1缓存的最佳情况延迟为4c,因此加载延迟损失为1.4c。但为什么有效加载延迟不是4c呢?

调度器将预测微操作所依赖的负载将在某个恒定的等待时间内完成,因此它将相应地调度微操作。如果由于任何原因(例如L1未命中),加载花费的时间比这个时间长,则微操作将被调度,但是加载结果还没有到达。在这种情况下,微操作将被重放,并且被分派的微操作的数量将大于被发出的微操作的总数。

负载和STA uops只能分派到端口2或3。事件UOPS_EXECUTED_PORT.PORT_2UOPS_EXECUTED_PORT.PORT_3可用于分别计算分派到端口2和3的uops数量。

案例1:UOPS_EXECUTED_PORT。端口2UOPS_EXECUTED_PORT。端口3=~2uops/iter
第二种情况:UOPS_EXECUTED_PORT。端口2UOPS_EXECUTED_PORT。PORT_3=~6uops/iter
案例3:UOPS_EXECUTED_PORT。端口2UOPS_EXECUTED_PORT。端口3=~4.2uops/iter

在案例1中,AGU UOP的派遣总数正好等于AGU UOP的退役数;没有重播。所以调度程序从来不会预测失误。在情况2中,每个AGU微操作平均有2次重放,这意味着每个AGU微操作平均有两次调度器预测失误。为什么案例2会有预测失误,而案例1却没有?

由于以下任何原因,调度程序将根据负载重播 uops:

  • L1 缓存未命中。
  • 内存消除歧义错误预测。
  • 内存一致性冲突。
  • L1 缓存命中,但有 L1-L2 流量。
  • 虚拟页码误判。
  • 其他一些(未记录的)原因。

使用相应的性能事件可以明确排除前5个原因。帕特里克·费伊(英特尔)说:

最后是的,在负载和存储之间切换时,有“几个”空闲周期。我被告知不要比“几个”更具体。
...
瑞士央行可以在同一周期读取和写入不同的银行。

我发现这些说法,也许是故意的,有点模棱两可。第一个语句表明,L1 的加载和html" target="_blank">存储永远不会完全重叠。第二个建议,只有当有不同的银行时,才能在同一周期内执行加载和存储。虽然去不同的银行可能既不是必要条件,也不是充分条件。但有一件事是肯定的,如果有并发加载和存储请求,则加载(和存储)可以延迟一个或多个周期。这解释了案例 1 中负载延迟的平均 1.4c 损失。

案例1和案例2之间有一个区别。在案例1中,依赖于相同负载uop的STA和加载uop在同一个周期中一起发出。另一方面,在案例2中,依赖于相同负载uop的STA和加载uop属于两个不同的问题组。每次迭代的问题停顿时间基本上等于顺序执行一个加载和退出一个存储所需的时间。每个操作的贡献可以使用CYCLE_ACTIVITY.STALLS_LDM_PENDING来估计。执行STA uop需要一个周期,因此存储可以在调度STA的周期之后立即退出。

平均加载延迟为< code>CYCLE_ACTIVITY。STALLS_LDM_PENDING 1周期(调度加载的周期)1周期(调度跳转微指令的周期)。我们需要向< code>CYCLE_ACTIVITY添加2个周期。STALLS _ LDM _挂起,因为在这些周期中没有执行暂停,但它们只占总加载延迟的一小部分。这等于6.8 ^ 2 = 8.8周期=~ 周期

在执行前十几个(或更多)迭代期间,每个周期都会在RS中分配一个跳转和STD uop。这些将始终在发布周期之后的周期中被分派执行。在某个时刻,RS将被填满,所有尚未分派的条目都将是STA和load uop,它们正在等待各自先前迭代的load uops完成(写回其结果)。因此,分配器将暂停,直到有足够的空闲RS条目来发布整个迭代。让我们假设最旧的load uop在周期T0时写回了它的结果。我将把该load uop所属的迭代称为当前迭代。将发生以下事件序列:

在周期 T0 处:调度当前迭代的 STA uop 和下一次迭代的负载 uop。此周期中没有分配,因为没有足够的 RS 条目。此周期计为分配停止周期,但不计为执行停止周期。

在周期 T1:STA uop 完成执行,商店停用。将分配要分配的下一个迭代的 uops。此周期计为执行停止周期,但不计为分配停止周期。

在循环T2:刚刚分配的跳转和STD uops被分派。此循环被计为分配失速周期,但不计为执行失速周期。

循环 T 3 至 T 3 CYCLE_ACTIVITY。STALLS_LDM_PENDING - 2:所有这些周期都算作执行和分配停止周期。请注意,有CYCLE_ACTIVITY。STALLS_LDM_PENDING - 1个周期在这里。

因此,UOPS_ISSUED.STALL_CYCLES应该等于1 0 1CYCLE_ACTIVITY.STALLS_LDM_PENDING-1。让我们检查一下:7.9=1 0 1 6.8-1。

根据案例1的推理,<代码>周期 应等于<代码> UOPS_ISSUED。STALL_CYCLES 1 = 7.9 1 =~实际测量的< code >周期数。当同时执行加载和存储时产生的损失比情况1高3.6c。就好像负载正在等待提交存储。我想这也解释了为什么案例2有回放而案例1没有。

在情况3中,有1个STD、1个STA、2个负载和1个跳转。单个迭代的uop都可以在一个周期内分配,因为IDQ-RS带宽是每个周期4个融合uop。uop在进入RS时未熔断。1 STD需要调度1个周期。跳跃也需要1个周期。有三个AGU uop,但只有两个AGU端口。因此,发送AGU uop需要2个周期(与情况1和2中的1个周期相比)。派遣的AGU uop组将是以下之一:

    < li >同一迭代的第二个加载uop和STA uop。这些依赖于同一迭代的第一个装载uop。两个AGU端口都被使用。 < li >下一个迭代的第一个加载uop可以在下一个周期调度。这取决于前一次迭代的负载。两个AGU端口中只有一个被使用。

由于要释放足够的RS条目以容纳整个问题组,还需要一个周期,UOPS_ISSUED。STALL_CYCLES1-1=UOPS_ISSUED(阶梯周期)。STALL_CYCLES=~平均加载延迟=~5.6c,这与案例1非常接近。代价约为1.6c。这就解释了为什么在案例3中,与案例1和案例2相比,每个AGU uop平均发送1.4次。

同样,由于释放足够的RS条目以容纳整个问题组需要更多的周期:

循环=~平均加载延迟1=6.6c/iter,这实际上与在我的系统上测量的cycles完全匹配。

类似于案例2的详细分析也可以在案例3中进行。在情况3中,STA的执行与第二次加载的等待时间重叠。两种负载的延迟也大部分重叠。

我不知道为什么不同案件的处罚不同。我们需要知道L1D缓存到底是如何设计的。无论如何,我有足够的信心在加载延迟(和存储延迟)上有“几个空闲周期”的损失来发布这个答案。

脚注

(1) 另外 80% 的时间都花在负载矩阵上。这种结构在手册中几乎没有提及。它用于指定 uop 和加载 uop 之间的依赖关系。据估计,SnB和IvB上有32个条目。没有记录在案的性能事件可以专门计算 LM 上的失速。所有记录的资源停滞事件均为零。在案例 3 中,每次迭代有 3 个 uops,这取决于之前的负载,因此很可能 LM 将在任何其他结构之前填充。据估计,在IvB和SnB上,RS条目的“有效”数量分别约为51和48。

(2)我可能在这里做了一个无害的简化。查看是否有可能为RESOURCE_STALLS。即使RS未满,RS事件也会发生?。

(3)通过管道创建uop流的可视化可能会有所帮助,以了解这一切是如何结合在一起的。您可以使用一个简单的负载链作为参考。这对于情况1很容易,但由于重播,对于情况2很困难。

分析方法 2:使用负载延迟性能监视工具

我想出了另一种方法来分析代码。这种方法要容易得多,但不太准确。然而,它基本上使我们得出了同样的结论。

另一种方法是基于MEM_TRANS_RETIRED.LOAD_LATENCY_*性能事件。这些事件的特殊之处在于它们只能在精确级别进行计数(参见:PERF STAT不计算内存负载,而是计算内存存储)。

例如,MEM_TRANS_RETIRED。LOAD_LATENCY_GT_4统计延迟大于“随机”选择的所有已执行负载样本的4个核心周期的负载数。延迟的测量方法如下。首次调度负载的周期是第一个被视为负载延迟一部分的周期。写回加载结果的周期是被视为延迟一部分的最后一个周期。因此,需要考虑重放。此外,从SnB(至少)开始,根据此定义,所有负载的延迟都大于4个周期。当前支持的最小延迟阈值为3个周期。

Case 1
Lat Threshold  | Sample Count
 3             | 1426934
 4             | 1505684
 5             | 1439650
 6             | 1032657      << Drop 1
 7             |   47543      << Drop 2
 8             |   57681
 9             |   60803
10             |   76655
11             |     <10      << Drop 3

Case 2
Lat Threshold  | Sample Count
 3             | 1532028
 4             | 1536547
 5             | 1550828
 6             | 1541661
 7             | 1536371
 8             | 1537337
 9             | 1538440
10             | 1531577
11             |     <10      << Drop

Case 3
Lat Threshold  | Sample Count
 3             | 2936547
 4             | 2890162
 5             | 2921158
 6             | 2468704      << Drop 1
 7             | 1242425      << Drop 2
 8             | 1238254
 9             | 1249995
10             | 1240548
11             |     <10      << Drop 3

了解这些数字表示随机选择的所有载荷样本的载荷数至关重要。例如,在所有负载的样本总大小为 1000 万,其中只有 100 万个具有大于指定阈值的延迟,则测量值为 100 万。但是,执行的负载总数可能为 10 亿。因此,绝对值本身没有多大意义。真正重要的是不同阈值的模式。

在情况1中,延迟大于特定阈值的负载数量有三次显著下降。我们可以推断,延迟等于或小于6个周期的负载是最常见的,延迟等于或者小于7个周期但大于6个循环的负载是第二常见的,大多数其他负载的延迟在8-11个周期之间。

我们已经知道最小延迟是4个周期。给定这些数字,估计平均负载延迟在4到6个周期之间是合理的,但更接近6个周期而不是4个周期。我们从方法1中知道平均负载延迟实际上是5.4c。所以我们可以使用这些数字做出相当好的估计。

在情况2中,我们可以推断出大多数负载的延迟小于或等于11个周期。平均加载延迟也可能远大于4,因为在很大的延迟阈值范围内,测得的加载数量是一致的。所以它介于4和11之间,但更接近11而不是4。从方法1中我们知道,平均加载延迟实际上是8.8c,这接近于基于这些数字的任何合理估计。

情况3类似于情况1,事实上,它们使用方法1确定的实际平均负载延迟对于这两种情况几乎相同。

使用< code > MEM _运输_退休进行测量。LOAD_LATENCY_*很容易,这种分析可以由对微体系结构了解不多的人来完成。

 类似资料:
  • 问题内容: 给定一个大表(10至1亿行),向其中添加一些额外的(未索引)列的最佳方法是什么? 只需添加列即可。 为每个额外的列创建一个单独的表,并在需要访问额外值时使用联接。 答案会根据额外的列是密集的(通常不是null)还是稀疏的(通常是null)而改变吗? 问题答案: 在大多数情况下,可以将具有值的列添加到行中,而无需更改数据页的其余部分。 NULL位图中 仅需设置一位。因此,是的,在大多数情

  • 下面的while循环会额外运行一段时间。我试图执行一个用户输入,从用户那里接受10个有效数字并打印它们的总和。然而,while循环执行额外的时间并请求第11个输入。 }

  • 本文向大家介绍wpf 附加的依赖项属性,包括了wpf 附加的依赖项属性的使用技巧和注意事项,需要的朋友参考一下 示例 何时使用 附加属性是一种依赖项属性,可以将其应用于任何属性,DependencyObject以增强了解该属性存在的各种控件或服务的行为。 附加属性的一些用例包括: 有一个父元素遍历其子元素并以某种方式作用于子元素。例如,Grid控制使用Grid.Row,Grid.Column,Gr

  • 我有一个Advision类,它使用了代理jar中的另一个类,但这个类依赖于代理jar中不存在的一个类。代理jar中不存在的这个类存在于应用程序类加载器上的另一个jar中。代理jar位于系统类加载器上。所以我没有遇到ClassDefounderRor。我已经尝试使用Transformer.forAdvise,这是在另一篇文章中建议的,但这只适用于Advise类。

  • 问题内容: 在我的Rails应用程序中,我具有允许查找与当前登录用户最接近的用户的功能。我为此使用了Geocoder gem。在用户模型中,我具有如下范围: 这非常有效,但是对于大量用户而言却很慢。当我调用此作用域时,它将生成以下sql查询: 我正在尝试为此创建索引,但它们不起作用。我正在尝试以下组合: 我应该如何添加索引以加快此查询的速度? 编辑:我忘记添加我的纬度和经度列是小数。 此查询的AN