for (int i = 0; i < 100000; ++i) {
int *page = mmap(NULL, PAGE_SIZE, PROT_READ | PROT_WRITE,
MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);
page[0] = 0;
munmap(page, PAGE_SIZE);
}
我希望在用户空间中获得~100000 dTLB-store-miss,每次迭代一次(同样~100000页错误和内核的dTLB-load-miss)。运行以下命令,结果大约是我期望的2倍。如果有人能澄清为什么会这样,我将不胜感激:
perf stat -e dTLB-store-misses:u ./test
Performance counter stats for './test':
200,114 dTLB-store-misses
0.213379649 seconds time elapsed
P.S。我已经验证并确定生成的代码不会引入任何可以证明此结果的理由。此外,我确实得到了~100000页错误和dTLB加载未命中:k。
答案 0 :(得分:7)
我希望在用户空间中获得~100000 dTLB-store-miss,每次迭代一次
我希望如此:
page[0] = 0;
,尝试加载包含page[0]
的缓存行,找不到TLB条目,递增dTLB-load-misses
,获取转换,实现页面“不存在”,然后生成页面错误。INVLPG
)。页面错误处理程序返回到导致错误的指令,以便可以重试它。page[0] = 0;
,尝试加载包含page[0]
的缓存行,找不到TLB条目,递增dTLB-load-misses
,获取翻译,然后修改缓存行。为了好玩,您可以使用MAP_POPULATE
标记和mmap()
来尝试让内核预先分配页面(并避免页面错误和第一次TLB未命中)。
答案 1 :(得分:3)
更新2 :我认为布兰登的答案是正确的。我应该删除这个,但我认为ocperf.py
建议对未来的读者仍然有用。并且它可以解释没有进程上下文标识符的CPU上的额外TLB未命中,其内核可以缓解崩溃。
更新:以下猜测错误。新的猜测:mmap
必须修改你的进程的页面表,所以也许有一些TLB失效的东西。我建议使用ocperf.py record
试图找出哪些 asm指令导致TLB未命中仍然存在。即使启用了优化,当推送/弹出glibc包装函数调用的返回地址时,代码也会存储到堆栈中。
也许你的内核有kernel / user page-table isolation enabled to mitigate Meltdown,所以从内核返回到用户时,所有TLB条目都已失效(通过修改CR3指向不包含内核映射的页表)。
在dmesg输出中查找Kernel/User page tables isolation: enabled
。如果您不介意在测试时容易受到Meltdown的攻击,可以尝试使用kpti=off
启动作为内核选项来禁用它。
因为您使用的是C,所以您通过glibc包装器使用mmap
和munmap
系统调用,而不是直接使用内联syscall
指令。该包装器中的ret
指令需要从堆栈中加载返回地址,而TLB错过了该地址。
额外的商店未命中可能来自推送返回地址的call
指令,虽然我不确定是否正确,因为当前堆栈页面应该已经来自前一个ret
的TLB系统调用。
您可以使用ocperf.py to get symbolic names for uarch-specific events进行个人资料分析。假设您使用的是最新的英特尔CPU, ocperf.py record -e mem_inst_retired.stlb_miss_stores,page-faults,dTLB-load-misses
,以查找哪些指令导致商店未命中。 (然后使用ocperf.py report -Mintel
)。如果report
无法轻松选择要查看的事件,请仅记录单个事件。
mem_inst_retired.stlb_miss_stores
是一个“精确”事件,与大多数其他存储TLB事件不同,所以计数应该是真实指令,而不是一些后来的指令,如不精确的perf事件。 (有关为什么某些性能计数器不能轻易精确的原因,请参阅Andy Glew's trap vs. exception answer;许多商店事件都不是。)