为什么修改指令会在x86上导致大量的i-cache和i-TLB丢失?

时间:2018-09-16 17:03:50

标签: performance x86 x86-64 performancecounter perf

下面的代码片段仅用一条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-icach-load-misses从11,738增加到805,516,067,这是一个多方面的增长。 还要注意,iTLB负载丢失从425个增长到4,857个,与L1 icache负载丢失相比增长了不少,但幅度较小。 运行时间从0.773433500秒增加到32.052301220秒,增长了41倍!

目前尚不清楚,如果指令占用空间太小,CPU为什么会导致i-cache丢失。这两个示例的唯一区别是修改了指令。授予L1 iCache和dCache是​​分开的,难道没有办法将代码安装到iCache中,从而可以避免缓存i-cache遗漏吗?

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

1 个答案:

答案 0 :(得分:2)

  

授予L1 iCache和dCache分开的权限,难道没有办法将代码安装到iCache中,从而可以避免缓存i-cache遗漏吗?

否。

如果您要修改代码-唯一可以通过的路径如下:

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

请注意,您还会错过μOP缓存。

this diagram 1 对此进行了说明,我认为它足够准确。

我怀疑iTLB丢失可能是由于定期的TLB冲洗所致。如果不做任何修改,您就不会受到iTLB丢失的影响,因为您的指令实际上来自μOP缓存。

如果没有,我不确定。我认为L1指令缓存实际上已得到解决,因此如果出现问题,则无需访问TLB。

1:很遗憾,该图片的版权限制非常严格,因此我避免突出显示路径/内嵌图片。