【发布时间】: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