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

时间:2018-01-31 22:16:09

标签: c performance performancecounter perf tlb

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。

2 个答案:

答案 0 :(得分:7)

  

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

我希望如此:

  • CPU尝试执行page[0] = 0;,尝试加载包含page[0]的缓存行,找不到TLB条目,递增dTLB-load-misses,获取转换,实现页面“不存在”,然后生成页面错误。
  • 页面错误处理程序分配页面并且(因为页面表已被修改)确保TLB条目无效(可能依赖于英特尔CPU不会缓存“不存在”页面这一事实,不一定是明确的做一个INVLPG)。页面错误处理程序返回到导致错误的指令,以便可以重试它。
  • CPU尝试第二次执行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包装器使用mmapmunmap系统调用,而不是直接使用内联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;许多商店事件都不是。)