我有一个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事件衡量),这意味着另一半上一百万是L1命中,在需求之前被预取。但是,包含mfence 的会导致一百万个RFO请求,从而产生RFO_HIT,这意味着高速缓存行仅在L2高速缓存中预取,而不再在L1高速缓存中预取。

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

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

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

最佳答案

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

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

但是,如果您确实禁用了L2拖缆,则计数与您期望的一样:即使没有L2.RFO_MISS,您也会看到大约1,000,000 L2.RFO_ALLmfence

首先,必须注意L2_RQSTS.RFO_*事件计数不计算源自L2流媒体的RFO事件,这一点很重要。您可以看到详细信息here,但是基本上每个0x24 RFO事件的umask都是:
name      umask
RFO_MISS   0x22
RFO_HIT    0x42
ALL_RFO    0xE2

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

似乎发生的情况是,当L2流媒体处于 Activity 状态时,您可能希望分配给其中一个事件的许多事件被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与存储指令块预取一起使用?,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/56117452/

10-12 14:51