Ana*_*ani 6 performance x86 intel prefetch memory-barriers
我有一个64字节大小的对象:
typedef struct _object{
int value;
char pad[60];
} object;
Run Code Online (Sandbox Code Playgroud)
在主要我正在初始化对象数组:
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();
Run Code Online (Sandbox Code Playgroud)
然后再次遍历每个元素。这是我正在为以下事件计数的循环:
int tmp;
for(int i=0; i < arr_size-105; i++){
array[i].value = 2;
//tmp = array[i].value;
_mm_mfence();
}
Run Code Online (Sandbox Code Playgroud)
拥有mfence在这里没有任何意义,但是我在捆绑其他东西,无意间发现,如果我有存储操作,而没有mfence,我将收到50万次RFO请求(以papi L2_RQSTS.ALL_RFO事件衡量),这意味着另外50万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))"
Run Code Online (Sandbox Code Playgroud)
但是,如果您确实禁用了 L2 流媒体,计数将如您所料:您会看到大约 1,000,000 个L2.RFO_MISS,L2.RFO_ALL甚至没有mfence.
首先,需要注意的是,L2_RQSTS.RFO_*事件计数不包括源自 L2 流媒体的 RFO 事件。您可以在此处查看详细信息,但基本上每个 0x24 RFO 事件的 umask 是:
name umask
RFO_MISS 0x22
RFO_HIT 0x42
ALL_RFO 0xE2
Run Code Online (Sandbox Code Playgroud)
请注意,没有一个 umask 值具有0x10指示应跟踪源自 L2 流媒体的事件的位。
似乎发生的情况是,当 L2 流送器处于活动状态时,您可能期望分配给这些事件之一的许多事件反而被 L2 预取器事件“吃掉”。可能发生的情况是,L2 预取器在请求流之前运行,并且当需求 RFO 从 L1 传入时,它发现 L2 预取器已经在处理请求。这只会再次增加事件的版本(事实上,umask |= 0x10当包括该位时,我得到了 2,000,000 总引用),这意味着RFO_MISS和RFO_HITRFO_ALL错过它。
这有点类似于“fb_hit”场景,其中 L1 加载既没有错过也没有准确命中,而是命中了正在进行的加载 - 但这里的复杂之处在于加载是由 L2 预取器启动的。
只是mfence减慢了一切速度,L2 预取器几乎总是有时间将线路一直带到 L2,从而给出RFO_HIT计数。
我认为这里根本不涉及 L1 预取器(事实上,如果你关闭它们,它的工作原理是一样的):据我所知,L1 预取器不与存储交互,只与加载交互。
您可以使用以下一些有用的perf命令来查看包含“L2 Streamer origin”位的差异。这是没有 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/
Run Code Online (Sandbox Code Playgroud)
其中包括:
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/
Run Code Online (Sandbox Code Playgroud)
我针对此代码运行了这些(与传递sleep(1)给--delay=1000perf 的命令对齐以排除初始化代码):
#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();
}
}
Run Code Online (Sandbox Code Playgroud)