内存占用是我们开发的时候需要重点关注的一个问题,我们可以人工根据代码推理出一个消耗内存较大的函数,也可以推理出大概会消耗多少内存,但是这种方法不仅麻烦,而且得到的只是推理的数据,而不是实际的数据。
我们可以使用工具来分析实际的内存消耗情况,分析较严重的内存消耗点,然后想办法修改。这篇文章我们就来聊一下在Linux系统中怎么使用valgrind分析C++程序内存资源使用情况。当然,在分析内存使用情况之前建议先解决内存泄漏问题,我们之前也介绍过怎么分析内存泄漏。
valgrind是一个工具组,valgrind里的massif工具可以用来分析内存使用情况,帮助识别程序中内存占用高的部分。
我们来看一个示例程序
cpp
#include <iostream>
#include <thread>
#include <chrono>
#include <string.h>
void FuncA()
{
void *p = malloc(1024);
memset(p, 'a', 1024); // 填充一些数据,防止内存处于伪分配的状态
free(p);
}
void FuncB()
{
void *p = malloc(1024 * 3);
memset(p, 'a', 1024 * 3);
free(p);
}
void FuncC()
{
void *p = malloc(1024 * 6);
memset(p, 'a', 1024 * 6);
free(p);
}
int main()
{
FuncA();
std::this_thread::sleep_for(std::chrono::milliseconds(10));
FuncB();
std::this_thread::sleep_for(std::chrono::milliseconds(10));
FuncC();
std::this_thread::sleep_for(std::chrono::milliseconds(10));
return 0;
}
这个程序很简单,所以我们完全可以直接分析出来实际内存使用情况,但是这里我们来看一下怎么用工具分析。
先执行命令g++ -g ./main.cpp -o main
将前面这段代码构建成可执行程序。
安装valgrind可以执行命令apt install valgrind
直接安装,然后执行命令valgrind --version
查看是否已经正常安装。
valgrind安装完成之后我们就可以执行命令valgrind --tool=massif --time-unit=ms ./main
启动分析了,解释一下这条命令中的参数:
--tool=massif
表示使用massif工具。--time-unit=ms
表示按照时间片抓取快照,还可以指定按照程序执行的指令数或者内存的变化量抓取快照。
这条命令执行完成后会生成一个分析结果文件
这个分析结果文件的文件名默认就是上图中的格式,后缀是进程号。
然后我们分析一下这个分析结果文件,valgrind有个自带的工具叫ms_print可以用来打开这种分析结果文件,比如执行命令ms_print ./massif.out.3679
就可以查看了。
但是这个可视化效果不好,我们可以使用另外一个工具massif-visualizer来分析,这个工具可以以火焰图的形式将结果文件展示出来。
可以执行命令apt install massif-visualizer
安装massif-visualizer工具,然后执行命令massif-visualizer --version
确认是否已经正常安装。
安装完成之后我们就可以使用massif-visualizer分析了,比如执行命令massif-visualizer ./main.massif.out
打开结果文件,这个工具的具体用法可以自己研究下
上面这种方法只能在程序执行完之后才能获取到分析结果文件,我们来看一下怎么在程序执行的过程中获取分析结果文件,需要使用gdb配合。
修改一下代码
cpp
#include <iostream>
#include <thread>
#include <chrono>
#include <string.h>
void FuncA()
{
void *p = malloc(1024);
memset(p, 'a', 1024); // 填充一些数据,防止内存处于伪分配的状态
free(p);
}
void FuncB()
{
void *p = malloc(1024 * 3);
memset(p, 'a', 1024 * 3);
free(p);
}
void FuncC()
{
void *p = malloc(1024 * 6);
memset(p, 'a', 1024 * 6);
free(p);
}
int main()
{
FuncA();
std::this_thread::sleep_for(std::chrono::milliseconds(10));
FuncB();
std::this_thread::sleep_for(std::chrono::milliseconds(10));
FuncC();
std::this_thread::sleep_for(std::chrono::milliseconds(10));
getchar();
return 0;
}
可以看到只是多了一个getchar()
,用这种方式来让程序不要直接退出。执行命令g++ -g ./main.cpp -o main
构建一下。
执行命令valgrind --vgdb=yes --tool=massif --time-unit=ms --massif-out-file=main.massif.out ./main
启动分析,解释一下这条命令中的参数:
- --vgdb=yes表示开启gdbserver。
- --massif-out-file=main.massif.out表示指定生成的分析结果文件名为main.massif.out。
可以看到程序正在执行,前面那个数字是进程号,等下需要用到。
然后我们另外开启一个命令窗口,执行命令gdb ./main
启动gdb调试。
然后在gdb中启动远程调试,比如执行命令target remote | /usr/bin/vgdb --pid=6004
,pid就是前面启动分析时的那个。
然后在gdb中执行命令monitor all_snapshot main.massif.vgdb.out
,这条命令可以驱动valgrind使用当前所有快照生成一个文件名为main.massif.vgdb.out的分析结果文件,在程序执行完之后也会生成一个分析结果文件。
需要注意的是在gdb中远程调试时进程会阻塞,可以在gdb中使用命令c
让程序继续执行,就是gdb正常调试时的指令。
如果这篇文章对你有帮助,别忘了关注我啊,可能我还会写出更多对你有帮助的文章