Valgrind: 这个代码诊断工具有点东西 : )

今天在网上逛街,发现了这样一个小玩意儿,感觉很有意思。

起因大概是,同学短短几行代码,不知道为什么在运行的时候会突破服务器大约4G的memory限制而被系统挂起。我一直觉得这件事情很难解决,bug只要涉及到内存管理,出了毛病就很难找到。我想这样的事情可能有望借助今天找到的Valgrind工具解决。


Valgrind: 这个诊断工具有点东西

有很多博客讲这个工具,比如说知乎CSDN,对于工具包massif,还有对应的可视化工具可以用massif-visualizer,等等。这里主要记一些我喜欢的认为比较常用的。

首先就是这个massif工具,根据关于它的介绍,它应该正好可以解决我的问题,就是由于内存管理不当导致的不那么容易显现的bug,比如说可以编译可以运行,从来不报错,但是就是内存占用越来越多这样的问题。它可以统计你的程序在运行过程中所占用的内存,并且告诉你这些内存大致是从哪里来的。

比如说这样一个简单的示例:

// File: main.cc
#include <vector>
int main()
{
	std::vector<int> a({});
	for(int i = 0; i < 10000; i ++)
	{
		a.push_back(1);
	}
	return 0;
}

将它以debug模式编译:

g++ -g ./main.cc

然后用valgrind运行产生的可执行文件:

valgrind --tool=massif ./a.out

就会得到一个输出文件,massif.out.1234,其中1234是进程id,这个文件可以用ms_print命令打开

ms_print massif.out.1234 |less

这里面的内容还是相对好懂的,文档写得也很详细。比较有意思的是文档9.2.6节中解释的这一部分内容:

--------------------------------------------------------------------------------
 n        time(B)         total(B)   useful-heap(B) extra-heap(B)    stacks(B)
--------------------------------------------------------------------------------
 10         10,080           10,080           10,000            80            0
 11         12,088           12,088           12,000            88            0
 12         16,096           16,096           16,000            96            0
 13         20,104           20,104           20,000           104            0
 14         20,104           20,104           20,000           104            0
99.48% (20,000B) (heap allocation functions) malloc/new/new[], --alloc-fns, etc.
->49.74% (10,000B) 0x804841A: main (example.c:20)
| 
->39.79% (8,000B) 0x80483C2: g (example.c:5)
| ->19.90% (4,000B) 0x80483E2: f (example.c:11)
| | ->19.90% (4,000B) 0x8048431: main (example.c:23)
| |   
| ->19.90% (4,000B) 0x8048436: main (example.c:25)
|   
 ->09.95% (2,000B) 0x80483DA: f (example.c:10)
 ->09.95% (2,000B) 0x8048431: main (example.c:23) 

The first four snapshots are similar to the previous ones. But then the global allocation peak is reached, and a detailed snapshot (number 14) is taken. Its allocation tree shows that 20,000B of useful heap memory has been allocated, and the lines and arrows indicate that this is from three different code locations: line 20, which is responsible for 10,000B (49.74%); line 5, which is responsible for 8,000B (39.79%); and line 10, which is responsible for 2,000B (9.95%).

它会告诉你在某个时间段,你代码的某某行造成了多少的内存占用,然后你就可以具体去检查这附近的内容。

我刚刚那小段代码搞出来有一部分就是这样:

--------------------------------------------------------------------------------
  n        time(i)         total(B)   useful-heap(B) extra-heap(B)    stacks(B)
--------------------------------------------------------------------------------
 15      2,206,004           72,976           72,960            16            0
 16      2,210,873           73,496           73,472            24            0
 17      2,211,410           73,496           73,472            24            0
99.97% (73,472B) (heap allocation functions) malloc/new/new[], --alloc-fns, etc.
->98.92% (72,704B) 0x48EFC19: ??? (in /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.28)
| ->98.92% (72,704B) 0x4011B89: call_init.part.0 (dl-init.c:72)
|   ->98.92% (72,704B) 0x4011C90: call_init (dl-init.c:30)
|     ->98.92% (72,704B) 0x4011C90: _dl_init (dl-init.c:119)
|       ->98.92% (72,704B) 0x4001139: ??? (in /usr/lib/x86_64-linux-gnu/ld-2.31.so)
|
->01.04% (768B) 0x10A2B9: __gnu_cxx::new_allocator<int>::allocate(unsigned long, void const*) (new_allocator.h:114)
  ->01.04% (768B) 0x109ED0: std::allocator_traits<std::allocator<int> >::allocate(std::allocator<int>&, unsigned long) (alloc_traits.h:444)
    ->01.04% (768B) 0x10993B: std::_Vector_base<int, std::allocator<int> >::_M_allocate(unsigned long) (stl_vector.h:343)
      ->01.04% (768B) 0x109B1E: void std::vector<int, std::allocator<int> >::_M_realloc_insert<int>(__gnu_cxx::__normal_iterator<int*, std::vector<int, std::allocator<int> > >, int&&) (vector.tcc:440)
        ->01.04% (768B) 0x1097A1: void std::vector<int, std::allocator<int> >::emplace_back<int>(int&&) (vector.tcc:121)          ->01.04% (768B) 0x1094F5: std::vector<int, std::allocator<int> >::push_back(int&&) (stl_vector.h:1201)
            ->01.04% (768B) 0x1092FB: main (main.cc:8)

可以看到有关于push_back方法部分的内存占用。

如果在代码中持续用new开辟新的内存而不delete的话,

#include <vector>

int main()
{
        std::vector<int> a({});
        //for(int i = 0; i < 1000; i++)
        while(true)
        {
                //a.push_back(1);
                int* b = new int;
        }
        return 0;
}

那么得到的结果就是这样:

--------------------------------------------------------------------------------
Command:            ./a.out
Massif arguments:   (none)
ms_print arguments: massif.out.5608
--------------------------------------------------------------------------------


    MB
781.3^                                                                       #
     |                                                                    :::#
     |                                                                 ::@:::#
     |                                                             ::::::@:::#
     |                                                         :@::::::::@:::#
     |                                                      ::::@::::::::@:::#
     |                                                   :::::::@::::::::@:::#
     |                                               :::@:::::::@::::::::@:::#
     |                                           ::@::::@:::::::@::::::::@:::#
     |                                        @::::@::::@:::::::@::::::::@:::#
     |                                    ::::@::::@::::@:::::::@::::::::@:::#
     |                                 ::@::::@::::@::::@:::::::@::::::::@:::#
     |                             :@::::@::::@::::@::::@:::::::@::::::::@:::#
     |                         @::::@::::@::::@::::@::::@:::::::@::::::::@:::#
     |                      :::@::::@::::@::::@::::@::::@:::::::@::::::::@:::#
     |                  :::::: @::::@::::@::::@::::@::::@:::::::@::::::::@:::#
     |              :::::::::: @::::@::::@::::@::::@::::@:::::::@::::::::@:::#
     |           @:::::::::::: @::::@::::@::::@::::@::::@:::::::@::::::::@:::#
     |       ::::@:::::::::::: @::::@::::@::::@::::@::::@:::::::@::::::::@:::#
     |    ::@::::@:::::::::::: @::::@::::@::::@::::@::::@:::::::@::::::::@:::#
   0 +----------------------------------------------------------------------->Gi

程序占用的内存会越来越多,这个图表可以用前面提到的massif-visualizer来可视化出来,效果还不错。

然后还可以看到:

--------------------------------------------------------------------------------
  n        time(i)         total(B)   useful-heap(B) extra-heap(B)    stacks(B)
--------------------------------------------------------------------------------
  7    134,598,125       85,957,976       14,386,912    71,571,064            0
  8    152,117,773       97,322,072       16,280,928    81,041,144            0
  9    169,637,421      108,686,168       18,174,944    90,511,224            0
 10    187,157,069      120,050,264       20,068,960    99,981,304            0
 11    204,676,717      131,414,360       21,962,976   109,451,384            0
16.71% (21,962,976B) (heap allocation functions) malloc/new/new[], --alloc-fns, etc.
->16.66% (21,890,272B) 0x10927B: main (main.cc:10)
|
->00.06% (72,704B) in 1+ places, all below ms_print's threshold (01.00%)

挺好玩的……

我还没有实际用上,等用上的时候再慢慢探索……

哦另外,如果是root程序的话,平常借助cling交互运行的方法可能不行了,要编译出来一个可执行文件的话,需要用到root-config之类的东西,从论坛上找到一个帖子,它是这么干的

`root-config --cxx --cflags` -g main.cxx

cxx换成cc,反正可以用就是了,好像可以通过这个来让g++也可以编译root的脚本,大概就是自动添加了相关的依赖库,以后再说吧。

上一篇:Flask中'endpoint'(端点)的理解


下一篇:67.canvas绘制常规图形