混淆简单C程序的缓存行为
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了混淆简单C程序的缓存行为相关的知识,希望对你有一定的参考价值。
我正在尝试一个程序,看看它的缓存行为是否与我的概念理解一致。
为此,我使用Perf命令:
perf stat -e cache-misses ./a.out
记录以下简单C程序的缓存缺失率:
int main() {
int N = 10000;
double *arr = malloc(sizeof(double) * N * N);
for(int i = 0; i < N; i++) {
for(int j = 0; j < N; j++){
arr[i * N + j] = 10.0;
}
}
return 0;
}
我的缓存缺失率为50.212%。如果我更改数组访问模式如下:
arr[j * N + i]
我得到缓存缺失率为22.206%。
这些结果令我感到惊讶。
- 对于具有非常规则的内存访问模式的这种简单程序,高速缓存未命中率为50.212%似乎非常高。我希望这更接近1 /(num-words-per-cache-line),肯定大于1/2。为什么缓存未命中率如此之高?
- 我对内存的理解(有限)表明以列主顺序迭代数组会导致更糟糕的缓存行为,但我得到的结果表明相反。这是怎么回事?
答案
答案非常简单:编译器可以优化您的分配。以下是代码的反汇编代码:
10 int main() {
0x00000000004004d6 <+0>: mov $0x2710,%edx
0x00000000004004db <+5>: jmp 0x4004e7 <main+17>
15 for(int j = 0; j < N; j++){
0x00000000004004dd <+7>: sub $0x1,%eax
0x00000000004004e0 <+10>: jne 0x4004dd <main+7>
14 for(int i = 0; i < N; i++) {
0x00000000004004e2 <+12>: sub $0x1,%edx
0x00000000004004e5 <+15>: je 0x4004ee <main+24>
10 int main() {
0x00000000004004e7 <+17>: mov $0x2710,%eax
0x00000000004004ec <+22>: jmp 0x4004dd <main+7>
16 arr[i * N + j] = 10.0;
17 }
18 }
19 return 0;
20 }
0x00000000004004ee <+24>: mov $0x0,%eax
0x00000000004004f3 <+29>: retq
正如您所看到的,没有为行arr[i * N + j] = 10.0;
生成汇编程序指令,因此您使用perf观察到的缓存未命中是无关的。
修复非常简单。只需将volatile
添加到指针声明中,强制编译器生成赋值,即:
volatile double *arr = malloc(sizeof(double) * N * N);
现在反汇编:
10 int main() {
0x0000000000400526 <+0>: sub $0x8,%rsp
11 int N = 10000;
12 volatile double *arr = malloc(sizeof(double) * N * N);
0x000000000040052a <+4>: mov $0x2faf0800,%edi
0x000000000040052f <+9>: callq 0x400410 <malloc@plt>
0x0000000000400534 <+14>: mov $0x0,%edx
16 arr[i * N + j] = 10.0;
0x0000000000400539 <+19>: movsd 0xc7(%rip),%xmm0 # 0x400608
0x0000000000400541 <+27>: jmp 0x40055f <main+57>
0x0000000000400543 <+29>: movslq %edx,%rcx
0x0000000000400546 <+32>: lea (%rax,%rcx,8),%rcx
0x000000000040054a <+36>: movsd %xmm0,(%rcx)
0x000000000040054e <+40>: add $0x1,%edx
15 for(int j = 0; j < N; j++){
0x0000000000400551 <+43>: cmp %esi,%edx
0x0000000000400553 <+45>: jne 0x400543 <main+29>
0x0000000000400555 <+47>: mov %esi,%edx
14 for(int i = 0; i < N; i++) {
0x0000000000400557 <+49>: cmp $0x5f5e100,%esi
0x000000000040055d <+55>: je 0x400567 <main+65>
0x000000000040055f <+57>: lea 0x2710(%rdx),%esi
0x0000000000400565 <+63>: jmp 0x400543 <main+29>
17 }
18 }
19 return 0;
20 }
0x0000000000400567 <+65>: mov $0x0,%eax
0x000000000040056c <+70>: add $0x8,%rsp
0x0000000000400570 <+74>: retq
而且还有更多的缓存未命中。
只运行一次测试可能会让你产生非常嘈杂的结果。您应该几次运行测量并取中位数。有基准框架,如Google Benchmark,所以请看一下。
最后一个。你的两个模式,如i * N + j
和j * N + i
都很容易被CPU预取器识别,因此两种情况下的缓存未命中率应该非常相似......
以上是关于混淆简单C程序的缓存行为的主要内容,如果未能解决你的问题,请参考以下文章
c_cpp 快速代码片段,用于在统计(阻止)/ dev / rdsk中的设备时验证fstat64和stat64的行为。
append() 在这个代码片段中是如何工作的?与特定变量混淆[重复]