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

以下代码片段仅使用一条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倍?

最佳答案

Granted the L1 iCache and dCache are separate, isn’t there a way to install code into iCache so that the cache i-cache misses can be avoided?

没有.

如果要修改代码 – 可以采用的唯一路径如下:

>存储日期执行引擎
> Store Buffer&转发
> L1数据缓存
>统一二级缓存
> L1指令缓存

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

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

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

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

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

转载注明原文:性能 – 为什么修改指令导致巨大的i-cache和i-TLB在x86上未命中? - 代码日志