Bee*_*ope 14 performance x86 cpu-cache perf intel-pmu
请考虑以下简单代码:
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <time.h>
#include <err.h>
int cpu_ms() {
return (int)(clock() * 1000 / CLOCKS_PER_SEC);
}
int main(int argc, char** argv) {
if (argc < 2) errx(EXIT_FAILURE, "provide the array size in KB on the command line");
size_t size = atol(argv[1]) * 1024;
unsigned char *p = malloc(size);
if (!p) errx(EXIT_FAILURE, "malloc of %zu bytes failed", size);
int fill = argv[2] ? argv[2][0] : 'x';
memset(p, fill, size);
int startms = cpu_ms();
printf("allocated %zu bytes at %p and set it to %d in %d ms\n", size, p, fill, startms);
// wait until 500ms has elapsed from start, so that perf gets the read phase
while (cpu_ms() - startms < 500) {}
startms = cpu_ms();
// we start measuring with perf here
unsigned char sum = 0;
for (size_t off = 0; off < 64; off++) {
for (size_t i = 0; i < size; i += 64) {
sum += p[i + off];
}
}
int delta = cpu_ms() - startms;
printf("sum was %u in %d ms \n", sum, delta);
return EXIT_SUCCESS;
}
Run Code Online (Sandbox Code Playgroud)
这将分配一个size
字节数组(在命令行中传入,在KiB中),将所有字节设置为相同的值(memset
调用),最后以只读方式循环遍历数组,跨越一个高速缓存行(64字节),并重复64次,以便每个字节被访问一次.
如果我们将预取关闭1,我们预计如果size
适合缓存,则在给定级别的缓存中达到100%,否则大多数会错过该级别.
我感兴趣的两个事件l2_lines_out.silent
和l2_lines_out.non_silent
(也是l2_trans.l2_wb
-但价值最终一致non_silent
),它会统计默默地从L2下降,不在行.
如果我们从16 KiB运行到1 GiB,并l2_lines_in.all
仅为最终循环测量这两个事件(加号),我们得到:
这里的y轴是事件的数量,标准化为循环中的访问次数.例如,16 KiB测试分配16 KiB区域,并对该区域进行16,384次访问,因此值为0.5意味着每次访问平均发生0.5个给定事件计数.
该l2_lines_in.all
行为几乎像我们所期待.它从零开始,当大小超过L2大小时,它会上升到1.0并保持在那里:每次访问都会产生一条线.
另外两行表现得很奇怪.在测试适合L3(但不在L2中)的区域中,驱逐几乎都是无声的.然而,一旦该地区进入主要记忆,驱逐都是非沉默的.
什么解释了这种行为?很难理解为什么来自L2的驱逐将取决于底层区域是否适合主存储器.
如果您执行存储而不是加载,几乎所有内容都是预期的非静默回写,因为更新值必须传播到外部缓存:
我们还可以使用mem_inst_retired.l1_hit
相关事件来查看访问所达到的缓存级别:
如果忽略了L1命中计数器,这些计数器在几个点上看起来不可能高(每次访问超过1 L1命中?),结果看起来或多或少与预期一致:当该区域完全适合L2时,大多数L2命中L3命中L3区域(我的CPU上最多6 MiB),然后错过了DRAM.
你可以在GitHub上找到代码.有关构建和运行的详细信息,请参阅自述文件.
我在Skylake客户端i7-6700HQ CPU上观察到了这种情况.Haswell 2似乎不存在相同的效果.在Skylake-X上,正如预期的那样,行为完全不同,因为L3缓存设计已经变为类似L2的受害者缓存.
1您可以在最近的英特尔上执行此操作wrmsr -a 0x1a4 "$((2#1111))"
.事实上,图表几乎与prefetch on 完全相同,因此将其关闭主要是为了消除混淆因素.
2有关详细信息,请参阅注释,但在l2_lines_out.(non_)silent
那里暂时不存在,但l2_lines_out.demand_(clean|dirty)
似乎有类似的定义.更重要的是l2_trans.l2_wb
,主要反映non_silent
在Skylake上的东西也存在于Haswell上并且看起来很镜像demand_dirty
,它也没有对Haswell产生影响.