为什么修改指令导致巨大的i-cache和i-TLB在x86上未命中?

问题描述 投票:5回答:1

以下代码片段仅使用一条RET指令创建一个函数(fun)。循环重复调用该函数并在返回后覆盖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 perf具有以下icache和iTLB统计信息:

perf stat -e L1-icache-load-miss -e iTLB-load-miss ./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;
}

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

perf stat -e L1-icache-load-miss -e iTLB-load-miss ./a.out

“./a.out”的效果统计信息统计信息:

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

   0.773433500 seconds time elapsed

请注意,覆盖该指令会导致L1-icache-load-miss从11,738增加到805,516,067 - 这是一个多方面的增长。另外请注意,iTLB负载未命中率从425增加到4,857 - 与L1-icache-load-miss相比相当增长但却更少。运行时间从0.773433500秒增加到32.052301220秒 - 增长41倍!

目前还不清楚,如果指令占用空间很小,CPU应该导致i-cache未命中。两个示例中唯一的区别是指令被修改。当然,L1 iCache和dCache是​​分开的,是不是有办法将代码安装到iCache中,这样可以避免缓存i-cache未命中?

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

performance x86 x86-64 performancecounter perf
1个回答
2
投票

当然,L1 iCache和dCache是​​分开的,是不是有办法将代码安装到iCache中,这样可以避免缓存i-cache未命中?

没有。

如果要修改代码 - 可以执行的唯一路径如下:

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

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

这由this diagram1说明,我认为这是足够准确的。

我怀疑iTLB未命中可能是由于常规TLB刷新。如果没有修改,您不会受到iTLB未命中的影响,因为您的指令实际上来自μOPcache。

如果他们不这样做,我不太确定。我认为L1指令高速缓存是虚拟寻址的,所以如果有命中则不需要访问TLB。

1:遗憾的是,该图像具有非常严格的版权,因此我不会突出显示图像的路径/内联。

© www.soinside.com 2019 - 2024. All rights reserved.