使用valgrind检查内存问题并且输出报告

本文介绍了如何使用Valgrind工具进行内存泄漏检测,并提供了详细的命令行参数解释。此外,还探讨了如何通过读取/proc/[pid]/statm文件来收集Linux进程的内存数据,以便进一步分析内存使用趋势。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

valgrind内存泄漏分析

是在linux中检查内存泄漏的工具。当程序编写完之后我一般都会使用它来检查一次内存问题。基本上能杜绝服务器的内存泄漏问题(当然是面对C/C++这样的语言的)。
使用方式就是将程序编译好,然后通过valgrind来启动程序。当测试完全部的内存测试用例之后,让服务器程序正常退出。在最后结束的时候将会输出服务器的内存泄漏报告。不过valgrind对于protobuf,stl这样的3方库的兼容性不算太好,所以会造成输出一堆的still reachable字样。其实完全没有必要去纠结这些问题。可以肯定这些都是误判的。当你使用了这样的库的情况下,一般都会需要将这些检查option关闭掉。防止自己被爆出来的一堆的错误唬住了。信息太多反而阻碍自己的判断。
下面是一般启动程序使用的指令:

valgrind --log-file=./valgrind_report.log --leak-check=full --show-leak-kinds=all --show-reachable=no --track-origins=yes ./

这个里面使用到的option分别的意义:

–log-file
指定报告输出文件
–track-origins=yes
是否显示未定义的变量,在堆、栈中被定义没有被initialised的变量都被定义成origins。默认是关闭这个option的。
–show-leak-kinds=all
这里可以支持的选项有[definite|possible],一般只需要去关注definite(绝逼),possible是可能会存在。
–leak-check=full
当服务器退出时是否收集输出内存泄漏,选项有[no|summary|full]这个地方我们将其设置成全输出,默认将会使用summary方式。

输出报告之后重点看definite的,而且关于自己程序的。其次再去看possible的内存分配点的释放。

收集linux进程内存数据

对于在linux中的进程内存使用在/proc/[pid]/statm文件中其实是写了进程的内存使用情况。如果我们想去分析一个进程的内存长时间使用的趋势,可以在crontab中写一个定时运行的脚本通过读取这个文件来定时采集服务器的内存使用情况。如果服务器中安装了mysql数据库,可以使用命令行将这些数据入库。然后通过chart.js+php将数据通过图表方式呈现出来。数据收集了之后,其实也是没法子判断出数据的趋势。所以得让数据图形化。

/proc/[pid]/status文件中我们可以抓取服务器的内存相关的信息(单位为bytes):

VmPeak:   393532 kB
VmSize:   393532 kB
VmLck:         0 kB
VmPin:         0 kB
VmHWM:     35012 kB
VmRSS:     35012 kB
VmData:   328460 kB
VmStk:       268 kB
VmExe:      2248 kB
VmLib:     12836 kB
VmPTE:       228 kB
VmSwap:        0 kB

VmPeak = 虚存历史最高值
VmSize = 虚存
VmHWM = 物理内存最高值
VmRSS = 物理内存
VmData, VmStk, VmExe = 数据、栈和文本代码内存
VmLib = 共享库代码内存大小
VmPTE = 全部页表内存大小
VmLck = 锁定内存大小(参考:mlock)
VmSwap = 交换

/proc/[pid]/statm文件中对于内存的描述如下(度量单位是页):
提供某个pid下的内存使用情况。下面是每页的含义:

列编号名称详解
1size全部程序的大小,就是top中的VIRT虚存大小
2resident程序使用的实存,在top中的Res
3share共享页大小
4text文本(代码)
5lib库(Linux2.6无用)
6data数据和堆
7dt脏页大小(Linux 2.6无用)

为了直观理解的方便,我们可以使用bytes方式。
接下来就是将入库之后的内存做出曲线图了。这里需要使用的技术是apache+php+chart.js。
我们只需要编写一个页面来实现这个功能。在web世界里面,php是跑在服务器段的,它负责去在服务器中执行并且获得返回信息。并且通过打印字符串通过apache将这些http的文本发给browse。当browse拿到http文本,如果读取到了js这种前段的脚本,就会根据文本在本地绘画。我们这个示例就是通过php去读取mysql中我们已经收集到的某个服务器的memory的历史使用趋势数据。并且将数据写入到http中,并且填充一个js脚本。

python采集脚本

先请自己去创建一个数据库,然后就是将这个python写成一个crontab的东西吧。如果方便可以在自己的服务器里面部署一套来玩玩。

#! /usr/bin/python
# -*- coding: utf8 -*-
import os,re


#  CREATE TABLE `MEM_LOG` (
#    `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
#    `pid` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '唯一id',
#    `VmPeak` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '虚存历史最高值',
#    `VmSize` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '虚存',
#    `VmHWM` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '物理内存最高值',
#    `VmRSS` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '物理内存',
#    `VmData` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '数据',
#    `VmStk` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '栈',
#    `VmExe` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '文本代码内存',
#    `VmLib` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '共享库代码内存大小',
#    `VmPTE` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '全部页表内存大小',
#    `update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '刷新时间',
#    PRIMARY KEY (`id`)
#  ) ENGINE=InnoDB ;

fields = [ 'VmPeak', 'VmSize', 'VmHWM', 'VmRSS', 'VmData', 'VmStk', 'VmExe', 'VmLib', 'VmPTE' ]
def read_pid_memory_info(pid):
    fd = open("/proc/%d/status"%pid,"r")
    record = {}
    record[ 'pid' ] = pid
    for line in fd.readlines():
        for field in fields:
            r = re.compile('%s: * *([0-9]+) kB'%field)
            r_result = r.findall( line )
            if len(r_result) == 0:
                continue
            record[ field ] = int( r_result[0] ) 
            pass
        pass
    fd.close()
    return record

def insert_into_db( record ):
    field_str = ''
    value_str = ''
    for field in record:
        field_str = field_str + ',' + field
        value_str = value_str + ',' + str( record[ field ] )
        pass
    field_str = field_str[1:]
    value_str = value_str[1:]
    sql = 'insert into MEM_LOG ( %s ) VALUES ( %s );'%( field_str, value_str )
    print( sql )
    prompt = 'mysql -uroot -pyourpassword mem_log_db -e "%s"'%(sql)
    os.system( prompt )
    pass

pip = os.popen( "ps aux | grep your_system | grep -v grep | awk '{print $2}'" )
all_proc = []
for line in pip.readlines():
    pid = int( line.replace('\n','') )
    print "pid is: %d"%pid
    record = read_pid_memory_info(pid)
    insert_into_db( record )
    pass

让他每10分钟统计一条出来:

*/10 * * * * /home/sean/memory_spy/spy.py

注意需要将这个spy.py设置成一个可执行的程序。

连接MySQL读取数据


<?
 $mysql_server_name="127.0.0.1"; //数据库服务器名称
    $mysql_username="xxx"; // 连接数据库用户名
    $mysql_password="xxx"; // 连接数据库密码
    $mysql_database="xxx"; // 数据库的名字

    // 连接到数据库
    $conn=mysql_connect($mysql_server_name, $mysql_username,
                        $mysql_password);

     // 从表中提取信息的sql语句
    $strsql="SELECT * FROM `MEM_LOG`";
    // 执行sql查询
    $result=mysql_db_query($mysql_database, $strsql, $conn);
    // 获取查询结果
    $row=mysql_fetch_row($result);



    $labels = array();
    $vm = array();
    $rm = array();
    $lua = array();

    mysql_data_seek($result, 0);
    // 循环取出记录
    while ($row=mysql_fetch_row($result))
    {
        array_push($labels, $row[0]);
        array_push($vm, $row[1]);
        array_push($rm, $row[2]);
        array_push($lua, $row[3]);
    }

    // 释放资源
    mysql_free_result($result);
    // 关闭连接
    mysql_close($conn);  

创建js的画布

注意:这里是下载了一份Chart.js-1.0.2放到了站点的根目录。否则会报错说这个js找不到。

<script src="Chart.js-1.0.2/Chart.js">
</script>
<canvas id="myChart" width="1800" height="400"></canvas>

通过php来填充数据

这个具体的实例,可以去抄一下chart.js的官方的一些实例就能去做饼状图,具状图之类的了。

    echo json_encode($labels);
    echo '<br/>';
    echo '
<script type="text/javascript">
var data = {
            labels: ' . " " .  json_encode($labels) . " " .  ',
            datasets: [
            {
            label: "Vm",
            fillColor: "rgba(220,220,220,0.2)",
            strokeColor: "rgba(220,220,220,1)",
            pointColor: "rgba(220,220,220,1)",
            pointStrokeColor: "#fff",
            pointHighlightFill: "#fff",
            pointHighlightStroke: "rgba(220,220,220,1)",
            data: ' . " " .  json_encode($vm) . " " .  '
            },
            {
            label: "rm",
            fillColor: "rgba(151,187,205,0.2)",
            strokeColor: "rgba(151,187,205,1)",
            pointColor: "rgba(151,187,205,1)",
            pointStrokeColor: "#fff",
            pointHighlightFill: "#fff",
            pointHighlightStroke: "rgba(151,187,205,1)",
            data: ' . " " .  json_encode($rm) . " " . '
            },
            {
            label: "lua",
            fillColor: "rgba(120,120,120,0.2)",
            strokeColor: "rgba(120,120,120,1)",
            pointColor: "rgba(120,120,120,1)",
            pointStrokeColor: "#fff",
            pointHighlightFill: "#fff",
            pointHighlightStroke: "rgba(220,220,220,1)",
            data: ' . " " .  json_encode($lua) . " " .  '
            }
            ]
            };
</script>';

设置图表的option,并且将数据画到画布上


<script type="text/javascript">
var options ={

            ///Boolean - Whether grid lines are shown across the chart
            scaleShowGridLines : true,

            //String - Colour of the grid lines
            scaleGridLineColor : "rgba(0,0,0,.05)",

            //Number - Width of the grid lines
            scaleGridLineWidth : 1,

            //Boolean - Whether to show horizontal lines (except X axis)
            scaleShowHorizontalLines: true,

            //Boolean - Whether to show vertical lines (except Y axis)
            scaleShowVerticalLines: true,

            //Boolean - Whether the line is curved between points
            bezierCurve : true,

            //Number - Tension of the bezier curve between points
            bezierCurveTension : 0.4,

            //Boolean - Whether to show a dot for each point
            pointDot : true,

            //Number - Radius of each point dot in pixels
            pointDotRadius : 4,

            //Number - Pixel width of point dot stroke
            pointDotStrokeWidth : 1,

            //Number - amount extra to add to the radius to cater for hit detection outside the drawn point
            pointHitDetectionRadius : 20,

            //Boolean - Whether to show a stroke for datasets
            datasetStroke : true,

            //Number - Pixel width of dataset stroke
            datasetStrokeWidth : 2,

            //Boolean - Whether to fill the dataset with a colour
            datasetFill : true,


            };


                    var ctx = document.getElementById("myChart").getContext("2d");
var myLineChart = new Chart(ctx).Line(data, options);

</script>

内存使用图

conclusion

最后如果你想将一个服务器的内存吃准了,其实还是来自于平日里对于服务器的把控。如同编制竹篓子,平时下功夫檫亮眼睛去多检查就扎实了。即使崩了也不会一泻千里的坏掉。

<think>好的,我现在需要帮用户解决Valgrind启动慢的问题。首先,用户使用的命令是valgrind --log-file=valgrind.log --show-leak-kinds=all --leak-check=full --track-origins=yes,这确实是比较全面的选项,但可能导致启动变慢。我得想想这些选项各自的作用,以及哪些可以调整来加快速度。 用户提到启动比较慢,可能是指Valgrind初始化或者程序启动的时间较长。Valgrind本身会模拟程序运行,所以本身就会慢,但可能有优化选项。首先,--leak-check=full和--track-origins=yes这两个选项会增加运行时的检查,可能在启动时就需要更多的初始化工作。比如,--track-origins=yes会跟踪未初始化内存的来源,这会增加额外的记录工作,可能导致启动延迟。 接下来,我需要考虑哪些选项可以关闭或调整来加快启动速度,同时尽可能保持有用的检测结果。比如,--leak-check=full可以改为--leak-check=summary,这样在结束时只汇总内存泄露情况,而不是详细报告每个泄露,可能减少一些处理时间。或者,如果用户暂时不关心内存泄露,可以暂时关闭--leak-check=no,但这可能不是用户想要的,毕竟用户可能还是需要检测泄露。 另外,--track-origins=yes可能会显著增加开销,尤其是在程序启动阶段需要初始化大量内存的情况下。如果用户的问题不是关于未初始化的值,可以尝试去掉这个选项,看看启动速度是否有改善。 另外,Valgrind还有一些其他的选项可能影响性能。比如,--vgdb=no可以禁用GDB服务器,减少一些开销。或者,使用--tool=memcheck之外的轻量级工具,但用户显然需要内存检查,所以可能不可行。 还有,用户将日志输出到文件,--log-file=valgrind.log是否会影响启动速度?可能影响较小,但可以考虑暂时不记录日志,或者输出到终端,但用户可能需要保存日志,所以这个可能不太容易调整。 另外,Valgrind的版本是否最新?有时新版本会有性能优化,建议用户更新到最新版本试试。 总结可能的优化步骤:首先去掉或调整--track-origins=yes,然后是调整--leak-check的级别,关闭部分检测。另外,可能还有其他选项,比如减少最大内存或线程数量,但这可能需要更深入的调整。或者,用户是否在调试大型程序,可以尝试先在小程序上测试,确认是否是程序本身的问题。 然后,需要将这些思路整理成用户容易理解的步骤,建议逐步尝试调整选项,观察启动时间的变化,找到影响最大的因素。同时,提醒用户Valgrind本身会显著降低程序运行速度,启动慢可能难以完全避免,但适当调整选项可以有所改善。</think>Valgrind 启动速度较慢通常与其高精度检测机制有关,但通过调整参数和运行环境可以部分优化。以下是分步优化建议: --- ### **一、关键参数调优** 1. **移除 `--track-origins=yes`** - **作用**: 此选项用于追踪未初始化内存的来源,需要额外记录栈信息,显著增加开销。 - **调整**: 若无需定位未初始化变量问题,可移除该选项。 - **效果**: 启动速度和运行速度会有明显提升。 2. **降低泄露检测粒度** - **原始参数**: `--leak-check=full`(详细报告每个泄露点) - **调整参数**: - `--leak-check=summary`(仅显示泄露统计摘要) - 或 `--leak-check=no`(完全关闭泄露检测) - **效果**: 减少初始化时的检测逻辑,加快启动。 --- ### **二、其他加速技巧** 3. **关闭冗余检测** - 添加 `--partial-loads-ok=yes`:跳过部分内存访问检查(可能降低检测精度)。 - 添加 `--undef-value-errors=no`:忽略未初始化值错误(仅限无需此类检测时使用)。 4. **减少日志写入延迟** - 原始参数: `--log-file=valgrind.log`(写入文件可能因磁盘 I/O 变慢) - 替代方案: - 直接输出到终端:移除 `--log-file`,通过重定向保存日志(如 `valgrind ... > valgrind.log 2>&1`)。 5. **限制检测范围** - 使用 `--vgdb=no`:禁用 GDB 调试集成,减少后台服务开销。 - 使用 `--num-callers=20`(默认 12):减少栈帧记录数量(需平衡精度)。 --- ### **三、环境与程序优化** 6. **使用最新版本 Valgrind** - 新版可能包含性能优化,例如 Valgrind 3.21+ 对多线程程序的改进。 7. **简化被检测程序** - 剥离无关依赖库(减少初始化代码)。 - 使用调试符号优化后的编译选项(如 `-g -O1`,避免 `-O0` 生成冗余代码)。 --- ### **四、示例优化命令** ```bash valgrind \ --log-file=valgrind.log \ --leak-check=summary \ --show-leak-kinds=definite \ --vgdb=no \ ./your_program ``` --- ### **五、重要提醒** - Valgrind 本质是牺牲速度换取检测精度,启动慢难以彻底解决。 - 若需高频调试,建议先缩小检测范围(如通过 `--input-fd` 控制输入),或结合其他工具(如 `AddressSanitizer`)互补使用
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值