【问题标题】:Why modifying an instruction cause huge i-cache and i-TLB misses on x86?为什么修改指令会在 x86 上导致巨大的 i-cache 和 i-TLB 未命中?
【发布时间】:2018-09-16 17:03:49
【问题描述】:

下面的代码片段创建了一个函数(有趣),只有一个 RET 指令。 循环反复调用函数,返回后覆盖RET指令的内容。

#include <sys/mman.h>
#include<stdlib.h>
#include<unistd.h>
#include <string.h>

typedef void (*foo)();
#define RET (0xC3)

int main(){
     // Allocate an executable page
    char * ins = (char *) mmap(0, 4096, PROT_EXEC|PROT_READ|PROT_WRITE, MAP_PRIVATE| MAP_ANONYMOUS, 0, 0);
    // Just write a RET instruction
    *ins = RET;
    // make fun point to the function with just RET instruction
    foo fun = (foo)(ins);
    // Repeat 0xfffffff times
    for(long i = 0; i < 0xfffffff; i++){
        fun();
        *ins = RET;
    }
    return 0;
}

X86 Broadwell 机器上的 Linux 性能具有以下 icache 和 iTLB 统计信息:

性能统计 -e L1-icache-load-misses -e iTLB-load-misses ./a.out

“./a.out”的性能计数器统计信息:

   805,516,067      L1-icache-load-misses                                       
         4,857      iTLB-load-misses                                            

  32.052301220 seconds time elapsed

现在,在不覆盖 RET 指令的情况下查看相同的代码。

#include <sys/mman.h>
#include<stdlib.h>
#include<unistd.h>
#include <string.h>

typedef void (*foo)();
#define RET (0xC3)

int main(){
    // Allocate an executable page
    char * ins = (char *) mmap(0, 4096, PROT_EXEC|PROT_READ|PROT_WRITE, MAP_PRIVATE| MAP_ANONYMOUS, 0, 0);
    // Just write a RET instruction
    *ins = RET;
    // make fun point to the function with just RET instruction
    foo fun = (foo)(ins);
    // Repeat 0xfffffff times
    for(long i = 0; i < 0xfffffff; i++){
        fun();
        // Commented *ins = RET;
    }
    return 0;
}

这是同一台机器上的性能统计数据。

性能统计 -e L1-icache-load-misses -e iTLB-load-misses ./a.out

“./a.out”的性能计数器统计信息:

        11,738      L1-icache-load-misses                                       
           425      iTLB-load-misses                                            

   0.773433500 seconds time elapsed

请注意,覆盖指令会导致 L1-icache-load-misses 从 11,738 增长到 805,516,067 - 多方面的增长。 另请注意,iTLB-load-misses 从 425 增长到 4,857 - 增长幅度很大,但与 L1-icache-load-misses 相比要少一些。 运行时间从 0.773433500 秒增长到 32.052301220 秒——增长了 41 倍!

如果指令占用空间如此之小,为什么 CPU 会导致 i-cache 未命中尚不清楚。这两个示例的唯一区别是修改了指令。既然 L1 iCache 和 dCache 是分开的,难道没有办法将代码安装到 iCache 中,从而避免缓存 i-cache 未命中吗?

此外,为什么 iTLB 未命中数增长了 10 倍?

【问题讨论】:

  • Stores 不进入 I-L1,因此当 CPU 检测到 SMC 时,它会使 L1 行无效,刷新管道并重新开始提取,导致未命中。至少我是这么相信的。 iTLB 计数可能是由于某些避免混叠的机制,因为还有一个相同的 dTLB 条目。但同样,我不确定。
  • 要了解更多关于真正的英特尔 CPU 如何处理自修改代码(使用管道核弹),请参阅Observing stale instruction fetching on x86 with self-modifying code。 @MargaretBloom:我进行了测试,即使在 Skylake 商店之后使用 mfence + lfence,我们也确实得到了 machine_clears.smc 的计数。我希望在商店可以驱逐 uop-cache 和 L1i 条目之前停止对另一页中的代码的猜测。

标签: performance x86 x86-64 performancecounter perf


【解决方案1】:

虽然 L1 iCache 和 dCache 是分开的,但有没有办法将代码安装到 iCache 中以避免缓存 i-cache 未命中?

没有。

如果你想修改代码 - 唯一可以走的路径是:

  1. 存储日期执行引擎
  2. 存储缓冲和转发
  3. L1 数据缓存
  4. 统一二级缓存
  5. L1 指令缓存

请注意,您也错过了 μOP 缓存。

this diagram1 对此进行了说明,我认为这已经足够准确了。

我怀疑 iTLB 未命中可能是由于定期 TLB 刷新造成的。在没有修改的情况下,您不会受到 iTLB 未命中的影响,因为您的指令实际上来自 μOP 缓存。

如果他们不这样做,我不太确定。我认为 L1 指令缓存是虚拟寻址的,因此如果命中则无需访问 TLB。

1:不幸的是,该图像的版权非常严格,因此我避免突出显示路径/内联图像。

【讨论】:

  • L1i 和 L1d 一样是 VIPT,但 uop 缓存是虚拟寻址的。 invlpg 也必须刷新相关的 uop-cache 行,但我猜只是 iTLB 条目的常规 LRU 驱逐可能会单独留下 uop 缓存。 (即 uop 缓存可以被认为是 CPU 中翻译缓存的一部分。)所以是的,如果 uop 缓存命中,iTLB 访问将会减少。
  • 另外,请注意 icache 未命中数大约是循环行程数的 3 倍。 805,516,067 / 0xfffffff = 3.00078。是什么解释了这 3 倍以上的 icache 未命中?
  • 顺便说一句,仅运行 41 倍的时间意味着在相同数量的循环迭代中运行 41 倍的中断处理程序,因此您可能会从中断处理程序中获得更多的 iTLB 未命中(尤其是内核中的 Meltdown 缓解措施) )。 IDK 如果这是合理的;我没有为一个运行那么长没有做SMC东西的简单循环计算数字。或者,也许您可​​以分析 iTLB-load-misses:u 以仅计算发生在用户空间中的事件?
  • @PeterCordes 是的,大多数 ITLB 未命中都发生在内核模式下,并且存在显着的标准偏差。 SMC 机器清除不刷新 TLB,只有 L1I 被刷新。 SMC 代码每次迭代运行 345 个周期(6 条指令),而非 SMC 代码每次迭代运行 5 个周期(5 条指令)。我没有发布答案的唯一原因是我坚持解释每次迭代的 3 次 icache 未命中。我期望有 2 个,一个用于包含循环体的行,一个用于包含fun 的行。但显然还有第三次失误。
  • @Peter Cordes,您对来自中断的 iTLB 未命中是正确的。请参阅下面的性能统计信息 iTLB-load-misses:u ``` perf stat -e L1-icache-load-misses -e iTLB-load-misses:u ./a.out './a. out': 805,517,291 L1-icache-load-misses 307 iTLB-load-misses 31.962878826 秒时间过去了```。抱歉格式化;用户空间中只有 307 个 iTLB-load-misses。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2022-07-13
  • 2011-11-30
  • 2015-10-19
  • 2017-10-23
  • 2019-04-26
相关资源
最近更新 更多