我正在使用Cachegrind来检索没有libc编译的静态程序的缓存未命中数(只有_start
调用我的main函数和asm中的退出系统调用)。该程序是完全确定的,指令和内存引用不会从一次运行更改为另一次运行。缓存与LRU完全关联,作为替换策略。
然而,我注意到有时候失误的次数会发生变化。更具体地说,在我去另一个目录之前,未命中的数量总是相同的:
% cache=8 && valgrind --tool=cachegrind --I1=$((cache * 64)),$cache,64 --D1=$((cache * 64)),$cache,64 --L2=262144,4096,64 ./adpcm
...
==31352== I refs: 216,145,010
...
==31352== D refs: 130,481,003 (95,186,001 rd + 35,295,002 wr)
==31352== D1 misses: 240,004 ( 150,000 rd + 90,004 wr)
==31352== LLd misses: 31 ( 11 rd + 20 wr)
如果我一次又一次地执行相同的命令,我会保持相同的结果。但是,如果我从不同的目录运行该程序:
% cd ..
% cache=8 && valgrind --tool=cachegrind --I1=$((cache * 64)),$cache,64 --D1=$((cache * 64)),$cache,64 --L2=262144,4096,64 ./malardalen2/adpcm
...
==31531== I refs: 216,145,010
...
==31531== D refs: 130,481,003 (95,186,001 rd + 35,295,002 wr)
==31531== D1 misses: 250,004 ( 160,000 rd + 90,004 wr)
==31531== LLd misses: 31 ( 11 rd + 20 wr)
我甚至从不同的目录得到了不同的结果。
我还使用Pin工具进行了一些实验,并且使用此工具我不需要更改目录以获得不同的值。但似乎可能的值集非常有限,与Cachegrind完全相同。
我的问题是:这些差异的根源可能是什么?
我的第一个提示是我的程序在内存中没有以相同的方式对齐,因此,在先前运行中存储在同一行中的一些变量不再存在。这也可以解释有限数量的组合。但我虽然cachegrind(和Pin)正在使用虚拟地址,但我认为操作系统(Linux)总是提供相同的虚拟地址。 还有其他想法吗?
编辑:您可以猜测读取LLd未命中,程序仅使用31个不同的缓存行。此外,缓存只能包含8个缓存行。因此即使在实际情况下,差异也无法通过第二次已经填充缓存的想法来解释(最多只有8行可以留在L1中)。
编辑2: Cachegrind的报告不是基于实际的缓存未命中(由性能计数器给出),而是模拟的结果。基本上,它模拟缓存的行为以计算未命中数。由于后果只是暂时的,因此完全正常,并允许更改缓存属性(大小,关联性)。
编辑3:我使用的硬件是Linux 3.2 x86_64上的Intel Core i7。编译标志是-static,对于某些程序-nostdlib(IIRC,我现在不在家)。
答案 0 :(得分:4)
Linux实现"地址空间布局随机化"安全问题的技术(http://en.wikipedia.org/wiki/Address_space_layout_randomization)。您可以像这样停用此行为:
echo -n "0" > /proc/sys/kernel/randomize_va_space
您可以通过此示例测试:
#include <stdio.h>
int main() {
char a;
printf("%u\n", &a);
return 0;
}
您应始终打印相同的值。
<强>之前:强>
% ./a.out
4006500239
% ./a.out
819175583
% ./a.out
2443759599
% ./a.out
2432498159
<强>后:强>
% ./a.out
4294960207
% ./a.out
4294960207
% ./a.out
4294960207
% ./a.out
4294960207
这也解释了不同的缓存未命中量,因为同一行中的两个变量现在可以在两个不同的行中。
编辑:这并不能完全解决问题,但我认为这是其中一个原因。我会向任何可以帮我解决这个问题的人提供赏金。
答案 1 :(得分:2)
这似乎是valgrind中已知的行为:
我使用了输出缓存基地址的示例,我也禁用了布局随机化。
我运行了两次可执行文件,在两次运行中得到相同的结果:
D refs: 40,649 (28,565 rd + 12,084 wr)
==15016== D1 misses: 11,465 ( 8,412 rd + 3,053 wr)
==15016== LLd misses: 1,516 ( 1,052 rd + 464 wr)
==15016== D1 miss rate: 28.2% ( 29.4% + 25.2% )
==15016== LLd miss rate: 3.7% ( 3.6% + 3.8% )
villar@localhost ~ $ cache=8 && valgrind --tool=cachegrind --I1=$((cache * 64)),$cache,64 --D1=$((cache * 64)),$cache,64 --L2=262144,4096,64 ./a.out
==15019== D refs: 40,649 (28,565 rd + 12,084 wr)
==15019== D1 misses: 11,465 ( 8,412 rd + 3,053 wr)
==15019== LLd misses: 1,516 ( 1,052 rd + 464 wr)
==15019== D1 miss rate: 28.2% ( 29.4% + 25.2% )
==15019== LLd miss rate: 3.7% ( 3.6% + 3.8% )
根据cachegrind文档(http://www.cs.washington.edu/education/courses/cse326/05wi/valgrind-doc/cg_main.html)
另一件毫无价值的事情是结果非常敏感。更改&gt; valgrind.so文件的大小,正在分析的程序的大小,甚至其名称的长度可能会扰乱结果。变化很小,但如果程序发生变化,则不要期望完全&gt;可重复的结果。 虽然这些因素意味着你不应该相信结果是超精确的,但希望&gt;它们应该足够接近有用。
阅读本文后,我更改了文件名并获得了以下内容:
villar@localhost ~ $ mv a.out a.out2345345345
villar@localhost ~ $ cache=8 && valgrind --tool=cachegrind --I1=$((cache * 64)),$cache,64 --D1=$((cache * 64)),$cache,64 --L2=262144,4096,64 ./a.out2345345345
==15022== D refs: 40,652 (28,567 rd + 12,085 wr)
==15022== D1 misses: 10,737 ( 8,201 rd + 2,536 wr)
==15022== LLd misses: 1,517 ( 1,054 rd + 463 wr)
==15022== D1 miss rate: 26.4% ( 28.7% + 20.9% )
==15022== LLd miss rate: 3.7% ( 3.6% + 3.8% )
将名称改回“a.out”给了我与之前完全相同的结果。
请注意,更改文件名或路径会改变堆栈的基础!! 这可能是在阅读了Evgeny先生在之前的评论中所说的内容之后的原因
更改当前工作目录时,还会更改相应的环境变量(及其长度)。由于所有环境变量的副本通常都存储在堆栈上方,因此您可以获得不同的堆栈变量分配和不同的缓存未命中数。 (除了“PWD”之外,shell还可以改变其他一些变量)。
编辑:文档也说:
程序启动/关闭会调用许多不感兴趣的函数,只会使输出复杂化。很高兴以某种方式排除这些。
模拟缓存可能正在跟踪程序的开始和结束,因为它是变体的来源。