为什么在一级缓存中将MFENCE与存储指令块预取一起使用?

时间:2019-05-13 17:46:31

标签: performance x86 intel memory-barriers prefetch

我有一个64字节大小的对象:

typedef struct _object{
  int value;
  char pad[60];
} object;

主要是我正在初始化对象数组:

volatile object * array;
int arr_size = 1000000;
array = (object *) malloc(arr_size * sizeof(object));

for(int i=0; i < arr_size; i++){
    array[i].value = 1;
    _mm_clflush(&array[i]);
}
_mm_mfence();

然后再次遍历每个元素。这是我正在为以下事件计数的循环:

int tmp;
for(int i=0; i < arr_size-105; i++){
    array[i].value = 2;
    //tmp = array[i].value;
     _mm_mfence();
 }

在这里拥有mfence毫无意义,但我在捆绑其他东西,无意间发现,如果我进行了商店操作而没有mfence ,我将收到50万次RFO请求(以papi L2_RQSTS.ALL_RFO衡量)事件),这意味着又有50万首L1命中,是在需求之前预取的。但是包括mfence 会导致一百万个RFO请求,从而产生RFO_HIT,这意味着缓存行仅在L2缓存中预取,而不再在L1缓存中预取。

除了英特尔文档以某种方式另外说明这一事实:“可以在执行MFENCE指令之前,之中或之后将数据推测性地带入缓存”。我用 load操作进行了检查。。如果没有mfence,我的命中率最高为2000 L1,而通过mfence,我的命中率最高为100万L1(以papi MEM_LOAD_RETIRED.L1_HIT事件衡量)。缓存行已在L1中预取以用于加载指令。

因此,不应该包括mfence块预取。存储和加载操作几乎都需要花费相同的时间-不需5-6毫秒,而需20毫秒。我经历了有关mfence的其他问题,但未提及预取对它的预期行为,我没有看到足够好的理由或解释,为什么它仅通过存储操作会阻止L1缓存中的预取。还是我可能缺少某些功能描述?

我正在Skylake微体系结构上进行测试,但是与Broadwell进行了核对,并获得了相同的结果。

2 个答案:

答案 0 :(得分:3)

不是L1预取会导致您看到计数器值:即使禁用L1预取器,效果仍然存在。实际上,如果禁用除L2流媒体之外的所有预取器,效果仍然存在:

wrmsr -a 0x1a4 "$((2#1110))"

但是,如果您要做禁用了L2流光,则计数与您期望的一样:即使没有{{1,您也会看到大约1,000,000 L2.RFO_MISSL2.RFO_ALL }}。

首先,请注意mfence事件计数不对源自L2流媒体的RFO事件计数。您可以看到详细信息here,但是基本上每个0x24 RFO事件的umask都是:

L2_RQSTS.RFO_*

请注意,所有umask值都没有name umask RFO_MISS 0x22 RFO_HIT 0x42 ALL_RFO 0xE2 位,该位指示应该跟踪源自L2流媒体的事件。

似乎发生的事情是,当L2流媒体处于活动状态时,您可能希望分配给其中一个事件的许多事件被L2预取器事件“吃掉”了。可能发生的情况是L2预取器在请求流之前运行,并且当需求RFO来自L1时,它发现已经从L2预取器进行了请求。这只会再次增加事件的0x10版本(实际上,包括该位时,我总共获得2,000,000个引用),这意味着umask |= 0x10RFO_MISSRFO_HIT会错过它

这有点类似于“ fb_hit”方案,在该方案中,L1既未加载也未命中,但未加载,但遇到了正在进行的加载-但复杂的是加载是由L2预取器启动的。

RFO_ALL的速度降低得足够慢,以至于L2预取器几乎总是有时间将线路一直拉到L2,从而得到mfence计数。

>

我认为这里根本不涉及L1预取器(事实是,如果您将它们关闭,则效果相同):据我所知,L1预取器不与商店交互,仅加载。

以下是一些有用的RFO_HIT命令,您可以用来查看包含“ L2流媒体起源”位的区别。这里不包含L2流媒体事件:

perf

,其中包括:

perf stat --delay=1000 -e cpu/event=0x24,umask=0xef,name=l2_rqsts_references/,cpu/event=0x24,umask=0xe2,name=l2_rqsts_all_rfo/,cpu/event=0x24,umask=0xc2,name=l2_rqsts_rfo_hit/,cpu/event=0x24,umask=0x22,name=l2_rqsts_rfo_miss/

我针对此代码运行了这些命令(perf stat --delay=1000 -e cpu/event=0x24,umask=0xff,name=l2_rqsts_references/,cpu/event=0x24,umask=0xf2,name=l2_rqsts_all_rfo/,cpu/event=0x24,umask=0xd2,name=l2_rqsts_rfo_hit/,cpu/event=0x24,umask=0x32,name=l2_rqsts_rfo_miss/ 与传递给perf的sleep(1)命令对齐以排除初始化代码)

--delay=1000

答案 1 :(得分:1)

关于存储操作的情况,我已经在Haswell处理器上以四种不同的配置运行了相同的循环:

  • MFENCE + E:存储之后有一条MFENCE指令。所有硬件预取器均已启用。
  • E:没有MFENCE。所有硬件预取器均已启用。
  • MFENCE + D:存储之后有一条MFENCE指令。所有硬件预取器均已禁用。
  • D:没有MFENCE。所有硬件预取器均已禁用。

结果显示如下,这些结果通过存储数量(每个存储都位于不同的缓存行)中进行了标准化。它们在多次运行中具有确定性。

                                 | MFENCE + E |      E     | MFENCE + D |      D     |
    L2_RQSTS.ALL_RFO             |    0.90    |    0.62    |    1.00    |    1.00    |
    L2_RQSTS.RFO_HIT             |    0.80    |    0.12    |    0.00    |    0.00    |
    L2_RQSTS.RFO_MISS            |    0.10    |    0.50    |    1.00    |    1.00    |
    OFFCORE_REQUESTS.DEMAND_RFO  |    0.20    |    0.88    |    1.00    |    1.00    |
    PF_L3_RFO                    |    0.00    |    0.00    |    0.00    |    0.00    |
    PF_RFO                       |    0.80    |    0.16    |    0.00    |    0.00    |
    DMND_RFO                     |    0.19    |    0.84    |    1.00    |    1.00    |

前四个事件是核心事件,后三个事件是非核心响应事件:

  • L2_RQSTS.ALL_RFO:针对向L2发出的每个RFO请求发生。这包括来自已退休或以其他方式退休的商店的RFO请求,以及来自PREFETCHW的RFO请求。对于启用了硬件预取器的情况,事件计数少于预期的数目,这是标准化的数目。可以想到两个可能的原因:(1)某种形式的RFO在L1中命中,以及(2)事件被低估了。我们将通过检查其他事件的计数并回顾我们对L1D预取器的了解,来尝试找出原因。
  • L2_RQSTS.RFO_HITL2_RQSTS.RFO_MISS:发生在LFO中分别命中或未命中的RFO。在所有配置中,这些事件的计数之和等于L2_RQSTS.ALL_RFO
  • OFFCORE_REQUESTS.DEMAND_RFO:此事件的文档建议它应与L2_RQSTS.RFO_MISS相同。但是,请注意OFFCORE_REQUESTS.DEMAND_RFOL2_RQSTS.RFO_HIT的总和实际上等于1。因此,L2_RQSTS.RFO_MISS可能会被低估(因此L2_RQSTS.ALL_RFO也会被低估)。实际上,这是最可能的解释,因为英特尔优化手册(和其他英特尔文档)说,只有L2流媒体预取器才能跟踪商店。英特尔性能计数器手册在L2_RQSTS.ALL_RFO的描述中提到了“ L1D RFO预取”。这些预取信息可能是指尚未退休的商店中的RFO(请参阅对Why are the user-mode L1 store miss events only counted when there is a store initialization loop?的回答的最后部分)。
  • PF_L3_RFO:在触发来自L2流媒体预取器的RFO并且目标缓存结构仅是L3时发生。此事件的所有计数均为零。
  • PF_RFO:在触发来自L2流媒体预取器的RFO并且目标高速缓存结构是L2且可能是L3时发生(如果L3是含端点的,则该行也将被填充为L3好)。此事件的计数接近L2_RQSTS.RFO_HIT。在MFENCE + E的情况下,似乎100%的RFO已按时完成(在需求RFO达到L2之前)。在E情况下,25%的预取未按时完成或预取了错误的行。与MFENCE + E情况相比,L2中的RFO命中次数要多于E情况的原因是MFENCE指令延迟了以后的RFO,从而使L2的大多数超级队列条目可用于L2流媒体预取器。因此MFENCE确实使L2流媒体预取器性能更好。如果没有它,L2上将有许多飞行中的需求RFO,从而留下了少量的用于预取的超级队列条目。
  • DMND_RFO:与OFFCORE_REQUESTS.DEMAND_RFO相同,但看起来它可能被低估了。
  

我检查了装载操作。没有mfence我最多可以升至2000 L1   打,而有了mfence,我的L1打达到一百万   papi MEM_LOAD_RETIRED.L1_HIT事件)。缓存行已预取   L1用于加载指令。

关于装入操作的情况,以我的经验,MFENCE(或任何其他fence指令)对硬件预取器的行为没有影响。这里的MEM_LOAD_RETIRED.L1_HIT事件的真实计数实际上很小(<2000)。正在计数的大多数事件来自MFENCE本身,而不是负载。 MFENCE(和SFENCE)要求一直向存储控制器发送防护请求,以确保所有未决存储都已到达全局观察点。防护请求不算作RFO事件,但可能被算作多个事件,包括L1_HIT。有关此以及类似观察的更多信息,请参阅我的博客文章:An Introduction to the Cache Hit and Miss Performance Monitoring Events