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