gpt4 book ai didi

performance - 为什么在 L1 缓存中使用 MFENCE 和存储指令 block 预取?

转载 作者:行者123 更新时间:2023-12-04 02:49:15 25 4
gpt4 key购买 nike

我有一个大小为 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 导致 100 万个 RFO 请求,给出 RFO_HIT,这意味着缓存行仅在 L2 中预取,不再在 L1 缓存中。

除了英特尔文档以其他方式指出的事实之外:“数据可以在 MFENCE 指令执行之前、期间或之后推测性地进入缓存。”我查了 加载操作。 如果没有 mfence,我最多可以获得 2000 次 L1 命中,而使用 mfence,我可以达到 100 万次 L1 命中(使用 papi MEM_LOAD_RETIRED.L1_HIT 事件测量)。缓存行在 L1 中预取以用于加载指令。

因此,包含 mfence 阻止预取的情况不应该是这种情况。存储和加载操作几乎都花费相同的时间——没有 mfence 5-6 毫秒,有 mfence 20 毫秒。我解决了有关 mfence 的其他问题,但没有提到预取时它的预期行为,我没有看到足够好的理由或解释为什么它会在仅存储操作的情况下阻止 L1 缓存中的预取。或者我可能在 mfence 描述中遗漏了一些东西?

我正在 Skylake 微架构上进行测试,但是与 Broadwell 进行了检查并得到了相同的结果。

最佳答案

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

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

但是,如果您确实禁用了 L2 流媒体,则计数如您所料:您会看到大约 1,000,000 L2.RFO_MISSL2.RFO_ALL即使没有 mfence .

首先,需要注意的是 L2_RQSTS.RFO_*事件计数不计算源自 L2 流媒体的 RFO 事件。详情可查看 here ,但基本上每个 0x24 RFO 事件的 umask 是:
name      umask
RFO_MISS 0x22
RFO_HIT 0x42
ALL_RFO 0xE2

请注意,所有 umask 值都没有 0x10指示应跟踪源自 L2 流媒体的事件的位。

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

这有点类似于“fb_hit”场景,其中 L1 加载既没有错过也没有准确命中,而是命中了一个正在进行的加载——但这里的复杂之处在于加载是由 L2 预取器启动的。
mfence只是减慢了一切,以至于 L2 预取器几乎总是有时间将线路一直带到 L2,给出 RFO_HIT数数。

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

这里有一些有用的 perf您可以使用命令查看包含“L2 流光源”位的差异。这里没有 L2 流媒体事件:
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/

我针对此代码运行了这些(将 sleep(1) 与传递给 perf 以排除初始化代码的 --delay=1000 命令对齐):
#include <time.h>
#include <immintrin.h>
#include <stdio.h>
#include <unistd.h>

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

int main() {
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((const void*)&array[i]);
}
_mm_mfence();

sleep(1);
// printf("Starting main loop after %zu ms\n", (size_t)clock() * 1000u / CLOCKS_PER_SEC);

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

关于performance - 为什么在 L1 缓存中使用 MFENCE 和存储指令 block 预取?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/56117452/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com