每个mmap/access/munmap有两次TLB缺失

9
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-misses,每次迭代一个(内核中也有大约100000个页面错误和dTLB-load-misses)。 运行以下命令,结果大约是我期望的两倍。 我会感激如果有人能够澄清为什么会出现这种情况:

perf stat -e dTLB-store-misses:u ./test
Performance counter stats for './test':

           200,114      dTLB-store-misses

       0.213379649 seconds time elapsed

附言:我已经验证并确定生成的代码没有引入任何可以解释这个结果的东西。此外,我的确得到了大约 100000 个页面故障和 dTLB-load-misses:k。

2个回答

8
我预计在用户空间中会有约100,000个dTLB存储缺失,每次迭代都会出现一个。
我预计以下情况会发生:
- CPU尝试执行page [0] = 0;,尝试加载包含page [0]的高速缓存行,找不到其TLB条目,增加dTLB-load-misses,获取翻译,意识到该页面为“不存在”,然后生成页面故障。 - 页面故障处理程序分配页面,并(因为修改了页表)确保TLB条目无效(可能依靠Intel CPU不缓存“不存在”页面这一事实,而不一定是通过显式执行INVLPG来实现)。页面故障处理程序返回导致故障的指令,以便可以重试。 - CPU尝试第二次执行page [0] = 0;,尝试加载包含page [0]的高速缓存行,找不到其TLB条目,增加dTLB-load-misses,获取翻译,然后修改高速缓存行。
如果想玩一下,可以使用mmap()MAP_POPULATE标志尝试让内核预分配页面(避免页面故障和第一个TLB缺失)。

啊,是的,我敢肯定这是正确的。我忘记了mmap在没有使用MAP_POPULATE选项时不会修改页表,尽管原帖提到了页面错误。真是个失误。 - Peter Cordes

3

更新2: 我认为Brendan的回答是正确的。我应该可能删除这个,但我认为ocperf.py的建议对于未来的读者仍然有用。并且它可能会解释那些在没有进程上下文标识符的CPU和缓解了Meltdown漏洞的内核中额外的TLB失效。

更新: 下面的猜测是错误的。新的猜测: mmap需要修改进程的页表,因此可能会有一些TLB失效。我建议使用ocperf.py record来尝试找出哪些汇编指令引起了TLB失效。即使启用了优化,当调用glibc包装函数时,代码也会存储到堆栈中以推入/弹出返回地址。


可能你的内核开启了内核/用户分页表隔离以缓解Meltdown漏洞,所以从内核返回用户时,所有的TLB条目都被无效化了(通过将CR3修改为指向不包含内核映射的页表)。

在dmesg输出中查找Kernel/User page tables isolation: enabled。如果您不介意在测试时存在Meltdown漏洞,您可以尝试使用kpti=off作为内核选项来禁用它。


因为你正在使用C语言,所以你是通过它们的glibc包装器而不是直接使用inline syscall指令来使用mmapmunmap系统调用的。那个包装器中的ret指令需要从栈中加载返回地址,这会导致TLB失效。

额外的存储失误可能来自于call指令推送返回地址,尽管我不确定是否正确,因为当前堆栈页面应该已经在TLB中从上一个系统调用的ret中了。


您可以使用ocperf.py对特定体系结构事件进行符号化名称的分析来做性能分析。假设您使用的是最新的英特尔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事件不同,因此计数应该是针对实际指令的,而不是像不精确的性能事件一样可能是一些后续指令。 (有关为什么某些性能计数器不容易精确的详细信息,请参见Andy Glew的陷阱vs.异常答案;许多存储事件都不是这样。)

1
我正在使用4.5版的Linux内核,该版本没有KPTI(无论如何,我正在运行Haswell,它具有PCID,即使使用KPTI也不会刷新TLB)。 - Mohammad Hedayati
@Hedy:啊,我没有意识到KPTI补丁已经使用PCIDs来减少CR3修改。我知道这是可能的,但是如果我没记错的话,Linux以前根本不使用PCIDs,所以我认为这将是一个太大的变化,无法在短时间内得到测试。感谢您的反馈,让我知道我的猜测是错误的:P 我更新了我的答案以反映这一点(但除了找出哪些指令TLB-miss,从而使哪些页面失效之外,我没有其他好主意)。 - Peter Cordes

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接