使用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的文档,结果的可靠性还是有保证的。
目录
相关文章
|
6月前
|
存储 缓存 自然语言处理
深入PHP内核:理解Opcode缓存对性能的影响
【4月更文挑战第25天】 在提升PHP应用性能的众多策略中,Opcode缓存技术因其显著的效果和较低的复杂度而备受开发者青睐。本文将深入探讨Opcode缓存机制,解析其对PHP执行效率的提升原理,并通过实验数据展示启用Opcode缓存前后的性能差异。我们还将讨论几种流行的Opcode缓存工具,如APC、OpCache与APCu,并评估它们的优劣及适用场景,帮助开发者根据不同的项目需求做出合适的选择。通过本文,读者不仅能够了解Opcode缓存的工作原理,还能学会如何在实际项目中应用这一技术以优化PHP应用程序的性能。
|
1月前
|
存储 缓存 监控
聊聊JIT是如何影响JVM性能的!
聊聊JIT是如何影响JVM性能的!
|
3月前
|
存储 缓存 算法
缓存优化利器:5分钟实现 LRU Cache,从原理到代码!
嗨,大家好!我是你们的技术小伙伴——小米。今天带大家深入了解并手写一个实用的LRU Cache(最近最少使用缓存)。LRU Cache是一种高效的数据淘汰策略,在内存有限的情况下特别有用。本文将从原理讲起,带你一步步用Java实现一个简单的LRU Cache,并探讨其在真实场景中的应用与优化方案,如线程安全、缓存持久化等。无论你是初学者还是有一定经验的开发者,都能从中受益。让我们一起动手,探索LRU Cache的魅力吧!别忘了点赞、转发和收藏哦~
85 2
|
4月前
|
监控 安全 Java
JVM内存问题之排查Direct Memory泄漏有哪些常用方法
JVM内存问题之排查Direct Memory泄漏有哪些常用方法
123 2
|
6月前
|
存储 缓存 负载均衡
深入PHP内核:探索Opcode缓存对性能的影响
在现代Web开发中,提升应用性能始终是开发者追求的目标之一。PHP作为一种广泛使用的服务端脚本语言,其执行效率对网站性能有着直接的影响。本文将深入探讨PHP的Opcode缓存机制,分析Opcode缓存如何优化PHP代码执行流程,减少服务器资源消耗,并通过实验数据展示启用Opcode缓存对性能的具体影响。我们将比较不同的Opcode缓存方案,并讨论它们在实际项目中的适用场景与潜在限制。
|
Windows
由antimalware占用CPU引起的一系列问题及解决方法
由antimalware占用CPU引起的一系列问题及解决方法
434 0
|
缓存 Java C语言
一个导致JVM物理内存消耗大的Bug
一个导致JVM物理内存消耗大的Bug
一个导致JVM物理内存消耗大的Bug
|
缓存 算法 安全
小师妹学JVM之:cache line对代码性能的影响
小师妹学JVM之:cache line对代码性能的影响
小师妹学JVM之:cache line对代码性能的影响
|
存储 缓存 监控
聊聊JIT是如何影响JVM性能的
我们知道Java虚拟机栈是线程私有的,每个线程对应一个栈,每个线程在执行一个方法时会创建一个对应的栈帧,栈帧负责存储局部变量变量表、操作数栈、动态链接和方法返回地址等信息,每个方法的调用过程,相当于栈帧在Java栈的入栈和出栈过程但是栈帧的创建是需要耗费资源的,尤其是对于 Java 中常见的 getter、setter 方法来说,这些代码通常只有一行,每次都创建栈帧的话就太浪费了。另外,Java 虚拟机栈对代码的执行,采用的是字节码解释执行的方式,考虑到下面这段代码,变量 a 声明之后,就再也不被使用,要是按照字节码指令解释执行的话,就要做很多无用功。另外,我们知道垃圾回收器回收的目标区域主要
|
缓存 Ubuntu Linux
Linux buffer/cache内存占用过高
Linux内核会在内存将要耗尽的时候,触发内存回收的工作,以便释放出内存给急需内存的进程使用。一般情况下,这个操作中主要的内存释放都来自于对buffer/cache的释放。尤其是被使用更多的cache空间。
4242 0
Linux buffer/cache内存占用过高