Wil*_*ser 18 c linux performance microprocessors
我的任务是生成一定数量的数据缓存未命中和指令缓存未命中.我已经能够毫无问题地处理数据缓存部分.
所以我离开了生成指令缓存未命中.我不知道是什么导致这些.有人可以建议一种生成它们的方法吗?
我在Linux中使用GCC.
gbu*_*mer 19
正如人们所解释的那样,指令高速缓存未命中在概念上与数据高速缓存未命中相同 - 指令不在高速缓存中.这是因为处理器的程序计数器(PC)已跳转到尚未加载到缓存中的位置,或者由于缓存已填满而已刷新,并且该缓存行是为驱逐而选择的缓存行(通常是最近一次)用过的).
手动生成足够的代码以强制指令未命中比强制数据高速缓存未命中要困难得多.
获得大量代码的一种方法是编写一个生成源代码的程序.
例如,编写一个程序来生成一个带有巨大switch语句的函数(在C中)[Warning,untested]:
printf("void bigswitch(int n) {\n switch (n) {");
for (int i=1; i<100000; ++i) {
printf(" case %d: n += %d;\n", n, n+i/2);
}
printf(" }\n return n;}\n");
Run Code Online (Sandbox Code Playgroud)
然后你可以从另一个函数调用它,你可以控制它沿缓存线跳转的大小.
switch语句的属性是可以强制代码向后执行,或者通过选择参数以模式执行.因此,您可以使用预取和预测机制,或尝试对付它们.
可以应用相同的技术来生成许多函数,以确保缓存可以随意"破坏".所以你可能有bigswitch001,bigswitch002等.你可以使用你也生成的开关来调用它.
如果你可以使每个函数(大约)具有一定数量的i-cache行,并且还生成比缓存中更多的函数,则生成指令缓存未命中的问题变得更容易控制.
您可以通过转储汇编程序(使用gcc -S)或objdump .o文件来确切了解函数,整个switch语句或switch语句的每个分支的大小.因此,您可以通过调整case:语句数来"调整"函数的大小.您还可以通过明智地选择bigswitchNNN()参数来选择有多少缓存行被命中.
除了这里提到的所有其他方法之外,强制指令高速缓存未命中的另一种非常可靠的方法是具有自修改代码.
如果你写入内存中的代码页(假设你配置了操作系统允许这样做),那么相应的指令行缓存当然会立即变为无效,并且处理器被迫重新获取它.
顺便说一句,分支预测不会导致icache错过,而只是分支.每当处理器尝试运行最近未运行的指令时,您都会错过指令缓存.现代x86足够智能,可以按顺序预取指令,因此您不可能仅仅通过从一条指令到另一条指令的普通步行而错过icache.但是任何分支(有条件的或其他的)都会不按顺序跳转到新地址.如果新指令地址最近没有运行,并且不在您已运行的代码附近,则可能是超出缓存,并且处理器必须停止并等待指令从主RAM进入.这与数据缓存完全一样.
一些非常现代的处理器(最近的i7)能够在代码中查看即将到来的分支,并启动icache预取可能的目标,但许多不能(视频游戏控制台).从主RAM获取数据到icache完全不同于管道的"指令获取"阶段,这是分支预测的内容.
"指令获取"是CPU执行流水线的一部分,指的是将来自icache的操作码带入CPU的执行单元,在那里它可以开始解码和工作.这与"指令高速缓存"提取不同,"指令高速缓存"提取必须在许多周期之前发生,并且涉及高速缓存电路向主存储器单元发出请求以通过总线发送一些字节.第一个是CPU管道的两个阶段之间的交互.第二个是管道与内存缓存和主RAM之间的交互,这是一个更复杂的电路.这些名字容易混淆,但它们完全是分开的.
因此,导致指令缓存未命中的另一种方法是编写(或生成)许多非常大的函数,这样您的代码段就会很大.然后从一个函数到另一个函数疯狂地调用,这样从CPU的角度来看,你在整个内存中都在做疯狂的GOTO.