通过覆盖C标准库调用来测量堆使用情况

时间:2016-03-04 20:13:20

标签: c++ c++11 malloc c++14 dynamic-linking

参考以下代码

#include <cassert>
#include <vector>
#include <dlfcn.h>
#include <limits>
#include <map>
#include <algorithm>
#include <iostream>
using std::cout;
using std::endl;
using std::vector;

/*
 * Overload the malloc call
 */
int max_heap_usage = 0;
std::map<uintptr_t, int>& heap_memory_map;
void track_max_usage(std::map<uintptr_t, int> heap_memory_map, 
        int& max_heap_usage);

void* malloc(size_t size) {

    // get the original malloc function call
    static auto original_malloc = (decltype(&malloc)) dlsym(RTLD_NEXT, "malloc");

    // Get the pointer from malloc
    void* pointer = original_malloc(size);
    uintptr_t pointer_handle = reinterpret_cast<uintptr_t>(pointer);

    // assert that the pointer does not already exist in the memory map
    assert("memory should not exist in memory map before allocation" && 
            heap_memory_map.find(pointer_handle) == heap_memory_map.end());

    // add to bookkeeping
    heap_memory_map[pointer_handle] = size;
    track_max_usage(heap_memory_map, max_heap_usage);

    return pointer;
}

void* calloc(size_t count, size_t size) {

    // get the original calloc
    static auto original_calloc = (decltype(&calloc)) dlsym(RTLD_NEXT, "calloc");

    // get the pointer returned by calloc
    void* pointer = original_calloc(count, size);
    uintptr_t pointer_handle = reinterpret_cast<uintptr_t>(pointer);

    // assert that the memory has not been allocated before
    assert("memory should not exist in the memory map before allocation" && 
            heap_memory_map.find(pointer_handle) == heap_memory_map.end());

    // add to bookkeeping
    heap_memory_map[pointer_handle] = size * count;
    track_max_usage(heap_memory_map, max_heap_usage);

    return pointer;
}

void free(void* ptr) {

    // get the original free function
    static auto original_free = (decltype(&free)) dlsym(RTLD_NEXT, "free");
    uintptr_t pointer_handle = reinterpret_cast<uintptr_t>(ptr);

    // assert that the heap memory map already has the pointer
    assert("memory to be freed does not exist in the heap memory map" && 
                heap_memory_map.find(pointer_handle) != heap_memory_map.end());

    // add to bookkeeping
    heap_memory_map.erase(pointer_handle);

    // free the memory
    original_free(ptr);
}

/*
 * Inputs:  A map containing pointer values and the amount of heap memory used
 *          after that point
 *
 *          The variable that keeps track of the max memory usage till this
 *          point
 *
 * This function updates the variable to have the max value if the current
 * memory map dictates that the memory usage is greater than what it was
 * before.
 */
void track_max_usage(std::map<uintptr_t, int>& heap_memory_map, 
        int& max_heap_usage) {

    // loop through all keys and add up the values
    int sum {0};
    for (const auto ele : heap_memory_map) { sum += ele.second; }

    // assign to max
    max_heap_usage = std::max(max_heap_usage, sum);
}

int main() {
    vector<int> vec {1, 2, 3, 4};
    for (auto ele : vec) {
        cout << ele << endl;
    }

    cout << "Total heap usage " << max_heap_usage << endl;
    return 0;
}

我试图覆盖malloc,calloc和free调用,这样只要有堆分配,我就可以跟踪它。不知何故,vector类似乎没有在堆上分配任何内存。有人可以解释一下究竟发生了什么吗?我怎样才能达到预期的效果?

谢谢!

2 个答案:

答案 0 :(得分:5)

您发布的程序 - 称之为main.cpp - 编译不完整,因此无法完成 你想解释的令人失望的行为的程序:

error: 'heap_memory_map' declared as reference but not initialized
 std::map<uintptr_t, int>& heap_memory_map;
                       ^  

如果我们通过声明来解决这个问题:

std::map<uintptr_t, int> heap_memory_map;

然后我们有一个链接错误:

undefined reference to `track_max_usage(std::map<unsigned long, int, std::less<unsigned long>, std::allocator<std::pair<unsigned long const, int> > >, int&)'

因为声明:

void track_max_usage(std::map<uintptr_t, int> heap_memory_map, 
        int& max_heap_usage);

与定义不符:

void track_max_usage(std::map<uintptr_t, int>& heap_memory_map, 
        int& max_heap_usage) {
    ...
}

如果我们通过声明来解决这个问题:

void track_max_usage(std::map<uintptr_t, int>& heap_memory_map, 
        int& max_heap_usage);

然后我们成功编译和链接,至少如果我们不挑剔 关于标准一致性:

$ g++ -o prog -std=c++11 -Wall main.cpp -ldl

如果我们对标准一致性非常挑剔:

$ g++ -o prog -std=c++11 -Wall -pedantic main.cpp -ldl

然后仍然存在编译错误

main.cpp:20:25: error: declaration of ‘void* malloc(size_t)’ has a different exception specifier
 void* malloc(size_t size) {
                         ^
...
/usr/include/stdlib.h:466:14: error: from previous declaration ‘void* malloc(size_t) throw ()’
 extern void *malloc (size_t __size) __THROW __attribute_malloc__ __wur;
              ^
main.cpp: In function ‘void* calloc(size_t, size_t)’:
main.cpp:40:39: error: declaration of ‘void* calloc(size_t, size_t)’ has a different exception specifier
 void* calloc(size_t count, size_t size) {
                                       ^
...
/usr/include/stdlib.h:468:14: error: from previous declaration ‘void* calloc(size_t, size_t) throw ()’
 extern void *calloc (size_t __nmemb, size_t __size)
              ^
main.cpp: In function ‘void free(void*)’:
main.cpp:60:20: error: declaration of ‘void free(void*)’ has a different exception specifier
 void free(void* ptr) {
                    ^
...
/usr/include/stdlib.h:483:13: error: from previous declaration ‘void free(void*) throw ()’
 extern void free (void *__ptr) __THROW;

另外几只鲤鱼:

int并不保证存储堆块的大小。因此标准库说:

void* malloc(size_t size);
void* calloc(size_t num, size_t size);

而不是:

void* malloc(int size);
void* calloc(int num, int size);

因此,您拥有以下权利:

size_t max_heap_usage = 0;
std::map<uintptr_t, size_t> heap_memory_map;

此外,您真正想要的是void *的地图 - 值到大小, 并且没有理由不拥有这样的地图:

std::map<void *, size_t> heap_memory_map;

然后克制:

uintptr_t pointer_handle = reinterpret_cast<uintptr_t>(pointer);

可以免除。

继续我们所拥有的东西(并记住我们没有 确切地知道已经得到了什么)运行prog并不简单 没有统计任何堆分配;它崩溃了:

$ ./prog
Segmentation fault (core dumped)

如果您对此进行调试并仔细阅读了段跟踪的回溯,那么您将会这样做 看循环调用序列:

operator new(unsigned long)    
__gnu_cxx::new_allocator<std::_Rb_tree_node<std::pair<unsigned long const, int> > >::allocate  /usr/include/c++/5/ext/new_allocator.h  104
std::allocator_traits<std::allocator<std::_Rb_tree_node<std::pair<unsigned long const, int> > > >::allocate  /usr/include/c++/5/bits/alloc_traits.h  360
std::_Rb_tree<unsigned long, std::pair<unsigned long const, int>, std::_Select1st<std::pair<unsigned long const, int> >, std::less<unsigned long>, std::allocator<std::pair<unsigned long const, int> > >::_M_get_node  /usr/include/c++/5/bits/stl_tree.h  491
std::_Rb_tree<unsigned long, std::pair<unsigned long const, int>, std::_Select1st<std::pair<unsigned long const, int> >, std::less<unsigned long>, std::allocator<std::pair<unsigned long const, int> > >::_M_create_node<std::piecewise_construct_t const&, std::tuple<unsigned long const&>, std::tuple<> >(std::piecewise_construct_t const&, std::tuple<unsigned long const&>&&, std::tuple<>&&)  /usr/include/c++/5/bits/stl_tree.h  545
std::_Rb_tree<unsigned long, std::pair<unsigned long const, int>, std::_Select1st<std::pair<unsigned long const, int> >, std::less<unsigned long>, std::allocator<std::pair<unsigned long const, int> > >::_M_emplace_hint_unique<std::piecewise_construct_t const&, std::tuple<unsigned long const&>, std::tuple<> >(std::_Rb_tree_const_iterator<std::pair<unsigned long const, int> >, std::piecewise_construct_t const&, std::tuple<unsigned long const&>&&, std::tuple<>&&)  /usr/include/c++/5/bits/stl_tree.h  2170
std::map<unsigned long, int, std::less<unsigned long>, std::allocator<std::pair<unsigned long const, int> > >::operator[]  /usr/include/c++/5/bits/stl_map.h  483
malloc  /home/imk/develop/so/heap_track_orig/main.cpp  34
operator new(unsigned long) 

重复 ad adause 。所以程序循环直到它用完了堆栈。

这是由于致命的逻辑缺陷造成的。您将继续假设所有C ++动态内存管理 程序中的操作将委托给标准C库设施 malloccallocfree

嗯,至少其中一些是,特别是对operator new的调用 起源于

heap_memory_map[pointer_handle] = size;

当您分配堆映射的新元素时,委派给malloc。 哪个是你的 malloc。再次呼吁:

heap_memory_map[pointer_handle] = size;

然后operator new,然后回到malloc,依此类推到堆栈耗尽。

这是致命的逻辑缺陷,但激励性的假设也是不稳定的。 C ++标准甚至不需要operator newoperator delete的默认实现 malloc分别委托freemalloc/free。它没有 指定C ++中的动态内存管理与C的动态内存管理之间的任何关系 我在这里使用的C ++编译器(Linux,GCC)确实如此委托,和 您可能也是如此,但实施者可能会选择委托两者 new/deletemassif直接与OS API。

不要尝试滚动自己的堆分析。使用适当的堆分析器。 对于linux,首选堆分析器是Valgrind's massif。 您的发行版几乎肯定会提供Valgrind包,包括massif

这是我要用#include <vector> #include <iostream> using namespace std; int main() { vector<int> vec; for (int i = 0; i < 1000; ++i) { vec.push_back(i); } for ( ;vec.size(); vec.pop_back()) {} return 0; } 进行分析的程序,并检查它的最大堆使用情况:

<强>的main.cpp

$ g++ -g -o prog -Wall main.cpp

编译和链接:

valgrind

使用massif运行$ valgrind --tool=massif ./prog ==6479== Massif, a heap profiler ==6479== Copyright (C) 2003-2015, and GNU GPL'd, by Nicholas Nethercote ==6479== Using Valgrind-3.11.0 and LibVEX; rerun with -h for copyright info ==6479== Command: ./prog ==6479== ==6479==

massif.out.NNNN

默认情况下,{ - 1}}会输出堆配置文件。我发现massif.out.6479 并运行:

$ ms_print massif.out.6479 > heap_prof.txt

我查看heap_prof.txt并在第32行读到:

Number of snapshots: 29
 Detailed snapshots: [4, 14, 17, 20, 23, 26 (peak)] 

告诉我堆快照#26显示了峰值使用情况。我滚动到 快照#26,见:

--------------------------------------------------------------------------------
  n        time(i)         total(B)   useful-heap(B) extra-heap(B)    stacks(B)
--------------------------------------------------------------------------------
 24      2,049,029           74,768           74,752            16            0
 25      2,069,629           78,872           78,848            24            0
 26      2,070,679           78,872           78,848            24            0
99.97% (78,848B) (heap allocation functions) malloc/new/new[], --alloc-fns, etc.
->92.18% (72,704B) 0x4EB91FE: ??? (in /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.21)
| ->92.18% (72,704B) 0x4010608: call_init.part.0 (dl-init.c:72)
|   ->92.18% (72,704B) 0x4010719: _dl_init (dl-init.c:30)
|     ->92.18% (72,704B) 0x4000D08: ??? (in /lib/x86_64-linux-gnu/ld-2.21.so)
|       
->07.79% (6,144B) 0x401788: __gnu_cxx::new_allocator<int>::allocate(unsigned long, void const*) (new_allocator.h:104)
  ->07.79% (6,144B) 0x401665: __gnu_cxx::__alloc_traits<std::allocator<int> >::allocate(std::allocator<int>&, unsigned long) (alloc_traits.h:182)
    ->07.79% (6,144B) 0x4014B0: std::_Vector_base<int, std::allocator<int> >::_M_allocate(unsigned long) (stl_vector.h:170)
      ->07.79% (6,144B) 0x400F59: std::vector<int, std::allocator<int> >::_M_insert_aux(__gnu_cxx::__normal_iterator<int*, std::vector<int, std::allocator<int> > >, int const&) (vector.tcc:353)
        ->07.79% (6,144B) 0x400CC4: std::vector<int, std::allocator<int> >::push_back(int const&) (stl_vector.h:925)
          ->07.79% (6,144B) 0x400AEC: main (main.cpp:9)

因此该程序的最高记录堆消耗为78,872字节 为我的std::vector分配了(仅仅)6,144个字节。

答案 1 :(得分:3)

C ++标准库容器不使用malloc等直接使用&#34; allocator&#34;对象,通常作为模板参数提供。您可以查看提供自定义分配器,或者提供自定义operator new功能,如果您想为了测量目的而挂钩这样的东西。