Valgrind是一款用于内存调试、内存泄漏检测以及线程问题分析的套件。它由一系列的工具组成,适用于Linux、macOS等操作系统。下面简要介绍几个主要的Valgrind工具:
- Memcheck 这是Valgrind最常用的工具,用于检测程序中的内存错误,包括非法内存读写、使用未初始化的内存等。
- Cachegrind 用于检测程序的缓存行为,可以找出影响性能的缓存未命中等问题。
- Callgrind 执行程序时会收集调用数据,用于查找程序的代价高昂的部分。
- Massif 堆内存使用监视程序,有助于分析程序的内存使用情况。
- Helgrind 线程错误检测工具,可以检测出潜在的死锁以及其他线程同步问题。
使用Valgrind非常简单,无需修改被诊断的程序,不过为得到更好的结果,通常编译的时候需要加上-g选项。运行是,只需要在命令行中加上valgrind和工具名,后面跟上要运行的程序和参数即可。Valgrind会运行程序并收集诊断数据,随后生成详细的报告。这些报告对于调试程序、优化性能非常有帮助。当然,Valgrind也会带来一定的性能开销,根据工具的不同,执行会变慢数倍至数十倍。使用这个工具的时候基本上都是在找问题,所以运行慢并不是很大的问题。
Valgrind的网址:Valgrind HomeOfficial Home Page for valgrind, a suite of tools for debugging and profiling. Automatically detect memory management and threading bugs, and perform detailed profiling. The current stable version is valgrind-3.23.0.https://valgrind.org/
我们使用其中的Massif来观察一下程序运行过程中堆内存的使用。
一段简单的程序如下:
#include <iostream>
using namespace std;
class myVector
{
private:
double *ele;
int size;
public:
myVector(int sz);
~myVector();
double& operator[](int i)
{
if (i < 0 || i >= size)
{
throw "Index out of range";
}
return ele[i];
}
double operator[](int i) const
{
if (i < 0 || i >= size)
{
throw "Index out of range";
}
return ele[i];
}
int getSize() const
{
return size;
}
};
myVector::myVector(int sz) : size(sz)
{
ele = new double[size];
for (int i = 0; i < size; i++)
{
ele[i] = 0;
}
}
myVector::~myVector()
{
delete[] ele;
}
int main()
{
myVector v(2048);
v[0] = 1.0;
v[1] = 2.0;
v[2] = 3.0;
v[3] = 4.0;
v[4] = 5.0;
for (int i = 0; i < 5; i++)
{
cout << v[i] << " ";
}
cout << endl;
myVector v2(2048);
cout << v2.getSize() << endl;
return 0;
}
编译: g++ -o test_mem -g -O0 test_mem.cpp
运行程序:valgrind --tool=massif ./test_mem
会在目录下生成massif.out.xxxx (xxxx是进程号),这个就是报告文件。
使用ms_print massif.out.xxxx 命令可以显示报告的内容,例如下面显示了一部分:
显示了heap内存使用量和时间的关系,由于我们的程序太小了,大部分时间都花在了一开始的程序装载上,因此所有的内存使用都挤在最后一点。
手册中关于这个现象的解释和解决方法:
Why is most of the graph empty, with only a couple of bars at the very end? By default, Massif uses "instructions executed" as the unit of time. For very short-run programs such as the example, most of the executed instructions involve the loading and dynamic linking of the program. The execution of main
(and thus the heap allocations) only occur at the very end. For a short-running program like this, we can use the --time-unit=B
option to specify that we want the time unit to instead be the number of bytes allocated/deallocated on the heap and stack(s).
按照说明重新执行程序: valgrind --tool=massif --time-unit=B ./test_mem,再使用ms_print显示如下:
可以看到堆内存使用有了明显的变化。
接下来search了一下ubuntu的软件包,发现一个好用的工具,
sudo apt install massif-visualizer 安装上,然后用这个工具分别打开上面两次运行的记录文件:
这是第一次的,这么看果然清晰多了!
下面这个是第二次的,因为横轴,纵轴都是相同的单位(内存用量),所以看着是这种比较奇怪的图形。其实如果用这个工具的话,反而是上面按时间轴的记录更好理解一些。