使用valgrind检查cache命中率,提高程序性能

简介:
作者:gfree.wind@gmail.com
博客:blog.focus-linux.net   linuxfocus.blog.chinaunix.net
 
 
本文的copyleft归gfree.wind@gmail.com所有,使用GPL发布,可以自由拷贝,转载。但转载请保持文档的完整性,注明原作者及原链接,严禁用于任何商业用途。
======================================================================================================
Valgrind为一个debugging 和 profiling的工具包,检查内存问题只是其最知名的一个用途。今天介绍一下,valgrind工具包中的cachegrind。关于cachegrind的具体介绍,请参见valgrind的在线文档 http://www.valgrind.org/docs/manual/cg-manual.html

下面使用一个古老的cache示例:

  1. #include <stdio.h>
  2. #include <stdlib.h>

  3. #define SIZE 100

  4. int main(int argc, char **argv)
  5. {
  6.     int array[SIZE][SIZE] = {0};
  7.     int i,j;

  8. #if 1
  9.     for (= 0; i < SIZE; ++i) {
  10.         for (= 0; j < SIZE; ++j) {
  11.             array[i][j] = i + j;
  12.         }
  13.     }
  14. #else
  15.     for (= 0; j < SIZE; ++j) {
  16.         for (= 0; i < SIZE; ++i) {
  17.             array[i][j] = i + j;
  18.         }
  19.     }
  20. #endif

  21.     return 0;
  22. }
这个示例代码从很久就开始用于说明利用局部性来增加cache的命中率。传统的答案是第一个for循环的性能要优于第二个循环。
我使用条件编译,在没有打开任何优化开关的条件下,第一种情况生成文件为test1,第二种情况生成文件为test2。
下面是输出

  1. [fgao@fgao-vm-fc13 test]$ valgrind --tool=cachegrind ./test1
  2. ==2079== Cachegrind, a cache and branch-prediction profiler
  3. ==2079== Copyright (C) 2002-2009, and GNU GPL'd, by Nicholas Nethercote et al.
  4. ==2079== Using Valgrind-3.5.0 and LibVEX; rerun with -h for copyright info
  5. ==2079== Command: ./test1
  6. ==2079==
  7. ==2079==
  8. ==2079== I refs: 219,767
  9. ==2079== I1 misses: 614
  10. ==2079== L2i misses: 608
  11. ==2079== I1 miss rate: 0.27%
  12. ==2079== L2i miss rate: 0.27%
  13. ==2079==
  14. ==2079== D refs: 124,402 (95,613 rd + 28,789 wr)
  15. ==2079== D1 misses: 2,041 ( 621 rd + 1,420 wr)
  16. ==2079== L2d misses: 1,292 ( 537 rd + 755 wr)
  17. ==2079== D1 miss rate: 1.6% ( 0.6% + 4.9% )
  18. ==2079== L2d miss rate: 1.0% ( 0.5% + 2.6% )
  19. ==2079==
  20. ==2079== L2 refs: 2,655 ( 1,235 rd + 1,420 wr)
  21. ==2079== L2 misses: 1,900 ( 1,145 rd + 755 wr)
  22. ==2079== L2 miss rate: 0.5% ( 0.3% + 2.6% )

  23. [fgao@fgao-vm-fc13 test]$ valgrind --tool=cachegrind ./test2
  24. ==2080== Cachegrind, a cache and branch-prediction profiler
  25. ==2080== Copyright (C) 2002-2009, and GNU GPL'd, by Nicholas Nethercote et al.
  26. ==2080== Using Valgrind-3.5.0 and LibVEX; rerun with -h for copyright info
  27. ==2080== Command: ./test2
  28. ==2080==
  29. ==2080==
  30. ==2080== I refs: 219,767
  31. ==2080== I1 misses: 614
  32. ==2080== L2i misses: 608
  33. ==2080== I1 miss rate: 0.27%
  34. ==2080== L2i miss rate: 0.27%
  35. ==2080==
  36. ==2080== D refs: 124,402 (95,613 rd + 28,789 wr)
  37. ==2080== D1 misses: 1,788 ( 621 rd + 1,167 wr)
  38. ==2080== L2d misses: 1,292 ( 537 rd + 755 wr)
  39. ==2080== D1 miss rate: 1.4% ( 0.6% + 4.0% )
  40. ==2080== L2d miss rate: 1.0% ( 0.5% + 2.6% )
  41. ==2080==
  42. ==2080== L2 refs: 2,402 ( 1,235 rd + 1,167 wr)
  43. ==2080== L2 misses: 1,900 ( 1,145 rd + 755 wr)
  44. ==2080== L2 miss rate: 0.5% ( 0.3% + 2.6% )
结果有点出人意料,第一种情况在D1的命中率反而低于第二种情况。

这个结果其实是应该可以理解的。
1. 现在的CPU的cache是以line为单位的。这样,当数组的size不大时,第二种情况的循环,虽然没有使用局部性原则,但是并不会因此降低cache的命中率,并且可能可以迅速的将数据填到cache中
2. 现在的CPU的cache空间较大。这样,当数组的size不大时,即使没有使用局部性原则,也不会导致cache的频繁更新。
由于我对cache的理解,也比较粗浅,所以不能明确的指出这个结果的根本原因。根据上面的两个条件,基本上也可以理解为什么第二种情况更快。

为了使cachegrind的结果与传统的答案一样,我们就需要破坏上面两个条件。那么,现在将SIZE从100增大的1000。再次看一下输出结果:

  1. [fgao@fgao-vm-fc13 test]$ valgrind --tool=cachegrind ./test1
  2. ==2094== Cachegrind, a cache and branch-prediction profiler
  3. ==2094== Copyright (C) 2002-2009, and GNU GPL'd, by Nicholas Nethercote et al.
  4. ==2094== Using Valgrind-3.5.0 and LibVEX; rerun with -h for copyright info
  5. ==2094== Command: ./test1
  6. ==2094==
  7. ==2094==
  8. ==2094== I refs: 11,519,463
  9. ==2094== I1 misses: 617
  10. ==2094== L2i misses: 611
  11. ==2094== I1 miss rate: 0.00%
  12. ==2094== L2i miss rate: 0.00%
  13. ==2094==
  14. ==2094== D refs: 7,305,498 (6,038,310 rd + 1,267,188 wr)
  15. ==2094== D1 misses: 125,791 ( 621 rd + 125,170 wr)
  16. ==2094== L2d misses: 125,763 ( 595 rd + 125,168 wr)
  17. ==2094== D1 miss rate: 1.7% ( 0.0% + 9.8% )
  18. ==2094== L2d miss rate: 1.7% ( 0.0% + 9.8% )
  19. ==2094==
  20. ==2094== L2 refs: 126,408 ( 1,238 rd + 125,170 wr)
  21. ==2094== L2 misses: 126,374 ( 1,206 rd + 125,168 wr)
  22. ==2094== L2 miss rate: 0.6% ( 0.0% + 9.8% )

  23. [fgao@fgao-vm-fc13 test]$ valgrind --tool=cachegrind ./test2
  24. ==2095== Cachegrind, a cache and branch-prediction profiler
  25. ==2095== Copyright (C) 2002-2009, and GNU GPL'd, by Nicholas Nethercote et al.
  26. ==2095== Using Valgrind-3.5.0 and LibVEX; rerun with -h for copyright info
  27. ==2095== Command: ./test2
  28. ==2095==
  29. ==2095==
  30. ==2095== I refs: 11,519,463
  31. ==2095== I1 misses: 617
  32. ==2095== L2i misses: 611
  33. ==2095== I1 miss rate: 0.00%
  34. ==2095== L2i miss rate: 0.00%
  35. ==2095==
  36. ==2095== D refs: 7,305,498 (6,038,310 rd + 1,267,188 wr)
  37. ==2095== D1 misses: 1,063,300 ( 621 rd + 1,062,679 wr)
  38. ==2095== L2d misses: 116,261 ( 595 rd + 115,666 wr)
  39. ==2095== D1 miss rate: 14.5% ( 0.0% + 83.8% )
  40. ==2095== L2d miss rate: 1.5% ( 0.0% + 9.1% )
  41. ==2095==
  42. ==2095== L2 refs: 1,063,917 ( 1,238 rd + 1,062,679 wr)
  43. ==2095== L2 misses: 116,872 ( 1,206 rd + 115,666 wr)
  44. ==2095== L2 miss rate: 0.6% ( 0.0% + 9.1% )
对比红色的两行,第一种情况的miss率为1.7%,而第二种情况的miss率高达14.5%。现在符合了传统答案。

总结一下:
1. 我们可以使用cachegrind来检查cache的命中率,提高程序性能;
2. 尽信书不如无书。书中的一些结果面对现在的环境,很可能是错误的。毕竟IT技术更新太快。还是自己动手实践一下更好!


注:Valgrind对于cache的测量,只是一种模拟。但是按照valgrind的文档,结果的可靠性还是有保证的。
目录
相关文章
|
数据中心 Anolis
性能优化特性之:LSE指令集编译优化
本文介绍了倚天实例上的编译优化特性:LSE,并从优化原理、使用方法进行了详细阐述。
|
设计模式 测试技术 编译器
C++项目中打破循环依赖的锁链:实用方法大全(一)
C++项目中打破循环依赖的锁链:实用方法大全
1236 0
|
7月前
|
算法 测试技术 Swift
Kimi开源Moonlight-16B-A3B:基于Muon优化器的高效大模型,性能与效率双突破!
最近,Muon优化器在训练小规模语言模型方面展示了强大的效果,但其在大规模模型上的可扩展性尚未得到验证。
373 0
|
编译器 C语言
C语言宏定义(#define定义常量​、#define定义宏​、 带有副作用的宏参数、 宏替换的规则、 宏函数的对比)
C语言宏定义(#define定义常量​、#define定义宏​、 带有副作用的宏参数、 宏替换的规则、 宏函数的对比)
|
机器学习/深度学习 人工智能 并行计算
GPU 和 CPU 处理器的架构
CPU(中央处理器)和 GPU(图形处理单元)是计算机系统中最重要的两种处理器。它们各自的架构设计和技术体系决定了其在不同应用领域中的性能和效率。
515 1
|
人工智能 Rust 安全
WebAssembly运行时库(WASM runtime:wasmer 或 wasmtime)\将rust官方demo猜数字编译为WASI目标并使用Wasmer运行
WebAssembly运行时库(WASM runtime:wasmer 或 wasmtime)\将rust官方demo猜数字编译为WASI目标并使用Wasmer运行
251 2
|
SQL 存储 缓存
maxcompute的特点
【5月更文挑战第5天】maxcompute的特点
294 6
|
人工智能 Go
【2024美赛】E题(中英文):房产保险的可持续性Problem E: Sustainability of Property Insurance
【2024美赛】E题(中英文):房产保险的可持续性Problem E: Sustainability of Property Insurance
324 1
|
存储 缓存 程序员
C++ 中的内存对齐——理论篇
> 本文作为上篇主要介绍内存对齐的理论基础,后续的下篇将侧重于 C++ 语言层面的实践,敬请期待! ### TL;DR - 处理器以若干字节的块而不是单字节的粒度访问内存,因此对于未对齐的内存需要额外的访存及计算开销,导致性能更差。 - 原子操作和矢量运算指令要求内存地址必须是对齐的,否则可能导致程序死循环和数据错误。 - 编译器通过 padding 自动对结构中的字段进行对齐,用以向后兼容以
925 0
C++ 中的内存对齐——理论篇