C 同一程序在多次运行之间的不同缓存未命中计数

C 同一程序在多次运行之间的不同缓存未命中计数,c,memory,linux-kernel,cachegrind,C,Memory,Linux Kernel,Cachegrind,我使用Cachegrind来检索在没有libc的情况下编译的静态程序的缓存未命中数(只是调用我的主函数和asm中的exit syscall的\u start)。程序是完全确定的,指令和内存引用不会从一次运行更改为另一次运行。缓存作为替换策略与LRU完全关联 但是,我注意到未命中的数量有时会发生变化。更具体地说,在我转到其他目录之前,未命中的数量始终相同: % cache=8 && valgrind --tool=cachegrind --I1=$((cache * 64)),$

我使用Cachegrind来检索在没有libc的情况下编译的静态程序的缓存未命中数(只是调用我的主函数和asm中的exit syscall的
\u start
)。程序是完全确定的,指令和内存引用不会从一次运行更改为另一次运行。缓存作为替换策略与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)
如果我一次又一次地执行相同的命令,我将继续得到相同的结果。但如果我从其他目录运行此程序:

 % 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行)

编辑2:Cachegrind的报告不是基于实际的缓存未命中(由性能计数器给出),而是模拟的结果。基本上,它模拟缓存的行为以计算未命中的数量。由于结果只是暂时的,所以这是完全正确的,并且允许更改缓存属性(大小、关联性)

编辑3:我使用的硬件是Linux 3.2 x86_64上的Intel Core i7。编译标志是静态的,对于某些程序是nostlib(IIRC,我现在不在家)。

Linux为安全问题实现了“地址空间布局随机化”技术()。您可以像这样停用此行为:

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
这也解释了缓存未命中的不同数量,因为在同一行中的两个变量现在可以在两个不同的行中


编辑:这显然不能完全解决问题,但我认为这是原因之一。我会把赏金给任何能帮我解决这个问题的人。

这似乎是瓦尔格林的一个已知行为:

我使用了输出缓存基址的示例,我还禁用了布局随机化

我运行了两次可执行文件,在两次运行中得到相同的结果:

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文档()

另一件毫无价值的事情是,结果非常敏感。更改>valgrind.so文件的大小、正在分析的程序的大小、甚至其名称的长度都会影响结果。变化很小,但如果您的程序发生任何变化,不要期望完全>可重复的结果。 虽然这些因素意味着你不应该相信结果是超精确的,但希望>它们应该足够接近有用

读了这篇文章后,我更改了文件名,得到了以下信息:

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”给了我和以前完全一样的结果

请注意,更改文件名或其路径将更改堆栈的底部!!。 这可能是在阅读了埃夫根尼先生之前的评论后得出的结论


更改当前工作目录时,还可以更改相应的环境变量(及其长度)。由于所有环境变量的副本通常存储在堆栈的正上方,因此堆栈变量的分配不同,缓存未命中的数量也不同。(壳牌可以改变除“PWD”之外的一些其他变量)

编辑:文档还说明:

程序启动/关闭调用了许多不感兴趣的函数,只会使输出复杂化。以某种方式排除这些可能会很好


模拟缓存可能正在跟踪程序的开始和结束,因为它是变体的来源。

在第二次运行期间,缓存中充满了其他数据?@JoachimPileborg我可以返回到上一个目录,我将有另一个未命中计数,或者可能与以前相同。我认为cachegrind从一个空堆栈开始模拟缓存(31是冷未命中的数量)。当您更改当前工作目录时,您也会更改相应的环境变量(及其长度)。由于所有环境变量的副本通常存储在堆栈的正上方,因此堆栈变量的分配不同,缓存未命中的数量也不同。(shell可以更改除“PWD”之外的一些其他变量。)@EvgenyKluev没错,昨天我还尝试在程序开头使用变量和
\uu属性(aligned(64))
对齐堆栈。但这并没有解决我的问题。我认为,只有在拥有完全关联缓存的情况下,以这种方式对齐堆栈才能有所帮助。在只有1路、2路或4路缓存的情况下,仍然可以根据环境大小以不同的方式获得堆栈变量和静态变量的别名。您可以尝试根据缓存的大小而不是仅64字节来对齐它…如果我在cachegrind中运行此示例,则会有不同的值(对于instanc,为4278190991)