每个mmap / access / munmap有两个TLB-miss

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 

PS我已经validation并确定生成的代码没有引入任何可以certificate这个结果的东西。 此外,我确实得到~100000页错误和dTLB加载未命中:k。

我希望在用户空间中获得~100000 dTLB-store-miss,每次迭代一次

我希望如此:

  • CPU尝试执行page[0] = 0; ,尝试加载包含page[0]的缓存行,找不到它的TLB条目,增加dTLB-load-misses ,取出翻译,实现页面“不存在”,然后生成页面错误。
  • 页面error handling程序分配页面并(因为页面表已被修改)确保TLB条目无效(可能依赖于英特尔CPU不会缓存“不存在”页面的事实,不一定是通过显式执行INVLPG )。 页面error handling程序返回到导致错误的指令,以便可以重试。
  • CPU尝试执行page[0] = 0; 第二次,尝试加载包含page[0]的缓存行,找不到它的TLB条目,递增dTLB-load-misses ,取出转换,然后修改缓存行。

为了好玩,您可以使用带有mmap()MAP_POPULATE标志来尝试让内核预先分配页面(并避免页面错误和第一次TLB未命中)。

更新2 :我认为布兰登的答案是正确的。 我应该删除它,但我认为ocperf.py建议对未来的读者仍然有用。 它可能解释了没有进程上下文标识符的CPU上的额外TLB未命中,其中内核可以缓解Meltdown。

更新 :以下猜测错了。 新的猜测: mmap必须修改你的进程的页面表,所以也许有一些TLB失效的东西。 我建议使用ocperf.py record试图弄清楚哪些 asm指令导致TLB未命中仍然存在。 即使启用了优化,当推送/弹出glibc包装函数调用的返回地址时,代码也会存储到堆栈中。


也许你的内核启用了内核/用户页表隔离来缓解Meltdown ,所以在从内核返回到用户时,所有TLB条目都已失效(通过修改CR3指向完全不包含内核映射的页表) 。

查找Kernel/User page tables isolation: enabled在dmesg输出中Kernel/User page tables isolation: enabled 。 您可以尝试使用kpti=off作为内核选项启动以禁用它,如果您不介意在测试时容易受到Meltdown攻击。


因为您正在使用C,所以您通过glibc包装器使用mmapmunmap系统调用,而不是直接使用内联syscall指令。 该包装器中的ret指令需要从堆栈加载返回地址,TLB未命中。

额外的商店未命中可能来自推送返回地址的call指令,虽然我不确定是否正确,因为当前堆栈页面应该已经从前一次系统调用的ret中进入TLB。


您可以使用ocperf.py进行概要分析, 以获取特定于uarch的事件的符号名称 。 假设您使用的是最近的Intel CPU, ocperf.py record -e mem_inst_retired.stlb_miss_stores,page-faults,dTLB-load-misses以查找哪些指令导致存储未命中。 (然后使用ocperf.py report -Mintel )。 如果report无法轻松选择要查看的事件,则仅记录单个事件。

与大多数其他存储TLB事件不同, mem_inst_retired.stlb_miss_stores是一个“精确”事件,因此计数应该是真实指令,而不是一些稍后的指令,如不精确的perf事件。 (请参阅Andy Glew的陷阱与exception答案 ,了解有关为什么某些性能计数器不能轻易精确的原因;许多商店事件都不是。)