【问题标题】:Two TLB-miss per mmap/access/munmap每个 mmap/access/munmap 两个 TLB-miss
【发布时间】:2018-07-11 03:57:34
【问题描述】:
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)。运行以下命令,结果大约是我预期的 2 倍。如果有人能澄清为什么会这样,我将不胜感激:

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。

【问题讨论】:

    标签: c performance performancecounter perf tlb


    【解决方案1】:

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

    我希望:

    • CPU尝试做page[0] = 0;,尝试加载包含page[0]的缓存行,找不到它的TLB条目,增加dTLB-load-misses,获取翻译,意识到页面“不存在”,然后生成页面错误。
    • 页面错误处理程序分配一个页面并(因为页表已修改)确保 TLB 条目无效(可能依赖于 Intel CPU 无论如何都不会缓存“不存在”页面这一事实,不一定通过显式做一个INVLPG)。页面错误处理程序返回到导致错误的指令,以便重试。
    • CPU 再次尝试执行page[0] = 0;,尝试加载包含page[0] 的高速缓存行,找不到它的TLB 条目,递增dTLB-load-misses,获取翻译,然后修改高速缓存行。

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

    【讨论】:

    • 啊,是的,我敢打赌这是对的。我忘记了 mmap not 在没有MAP_POPULATE 的情况下修改页表,即使 OP 提到了页面错误。 derp.
    【解决方案2】:

    更新 2:我认为 Brendan 的回答是正确的。我也许应该删除它,但我认为ocperf.py 的建议对未来的读者仍然有用。并且它可能会解释在没有进程上下文标识符的 CPU 上出现额外的 TLB 未命中以及缓解 Meltdown 的内核。

    更新:下面的猜测是错误的。新的猜测:mmap 必须修改你进程的页表,所以也许有一些 TLB 使某些东西失效。我建议使用ocperf.py record 来尝试找出导致 TLB 未命中的哪些 asm 指令仍然有效。即使启用了优化,当推送/弹出 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 进行配置。假设您使用的是最新的 Intel 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's trap vs. exception answer 了解为什么某些性能计数器不容易精确的一些详细信息;许多商店事件并非如此。)


    【讨论】:

    • 我使用的是没有 KPTI 的 4.5 Linux 内核(无论如何我在 Haswell 上运行,它有 PCID,即使使用 KPTI 也不会刷新 TLB)。
    • @Hedy:啊,我没有意识到 KPTI 补丁已经使用 PCID 来减少 CR3 修改。我知道这是可能的,但 IIRC Linux 以前根本没有使用 PCID,所以我认为这将是一个太大的变化,无法如此快速地对其进行测试。感谢您的反馈,我的猜测是错误的:P 更新了我的答案以反映这一点(但除了找出哪些指令 TLB 未命中以及哪些页面无效之外,我没有任何其他好主意。)
    猜你喜欢
    • 2017-10-11
    • 2014-12-21
    • 2014-11-23
    • 2017-05-19
    • 2011-03-02
    • 1970-01-01
    • 2012-01-18
    • 2011-03-27
    • 1970-01-01
    相关资源
    最近更新 更多