如何释放由boost :: unordered_map占用的空闲内存?

3
我在一个boost::unordered_map中添加了大量元素,然后将它们全部删除。这时我发现程序所占用的内存为198MB(大于(64+4)*2M),而unordered_map的大小为0。
接着我测试了一个vector,但是没有出现这样的问题。为什么呢?
#include <iostream>
#include <boost/unordered_map.hpp>

template <int N>
struct big_struct {
    char c[N];
};

int main(void) {
    typedef big_struct<64> data_type;
    typedef boost::unordered_map<int, data_type*> map_type;

    map_type m;

    for (int i = 0; i < 2000 * 1000; i++) {
            m.insert(std::make_pair(i, new data_type));
    }   

    for (map_type::iterator it = m.begin(); it != m.end();) {
            delete it->second;
            it = m.erase(it);
    }   

    std::cout << "finish, map size " << m.size() << std::endl;
    pause();

    return 0;
}

你是否实际测试过是否存在内存泄漏? - juanchopanza
为了对两个事物进行有意义的比较,需要同时观察这两个事物。你是如何测试 std::vector 的?我的测试结果显示,std::vectorboost::unordered_map 的行为完全相同,正如预期的那样。 - n. m.
@juanchopanza,该程序已经通过了Valgrind的测试,不存在任何内存泄漏的可能性。 - superK
那么,现在没有什么可以做的了。操作系统掌控着一切。 - juanchopanza
2个回答

9

语言运行时会保留已分配的内存,因为您可能希望再次使用它。将数百万个小块返回给操作系统需要相当长的时间,并且会使程序运行变慢。

如果您有一个非常大的向量,那仍然只是一个单独的内存块。一些编译器会考虑在不再需要时返回这种内存。返回一个大块比一百万个小块更加高效。


1
这是一个明显的例子,说明当Boost成为C++的标准部分时会发生什么。
std::unordered_map

在释放内存方面,上述答案不正确

std::unordered_map可能在rehash发生之前不会释放任何内存。当rehash可能发生时有文档记录,如果您查看size()的文档,它只是指元素数量,如果您想了解实际大小的想法,则必须在映射中添加自定义分配器并计算已分配/已释放的字节数。

这很遗憾,因为行为未经记录(也没有一些API来控制它),您不知道实现是否释放内存(如果您将在一段时间内不使用地图,则较好),或者实现是否缓存内存(如果您将再次插入不同元素,则非常好)。

这使得以内存高效的方式使用无序映射变得非常困难。此外,这基本上留下了巨大的内存使用空间,而没有被记录(没有说明一个没有元素的映射可以占用几百兆字节)。

以下是用于对内存使用情况进行分析的自定义分配器:

#include <unordered_map> //c++ container but in practice the same of boost
#include <memory>
#include <iostream>
using namespace std;

size_t counter = 0;

template <typename T>
class countingAllocator: public std::allocator<T>
{
public:
    typedef size_t size_type;
    typedef T* pointer;
    typedef const T* const_pointer;

    template<typename _Tp1>
    struct rebind
    {
            typedef countingAllocator<_Tp1> other;
    };

    pointer allocate(size_type n, const void *hint=0){
           counter += n;
            return std::allocator<T>::allocate(n, hint);
    }

    void deallocate(pointer p, size_type n){
            counter -= n;
            return std::allocator<T>::deallocate(p, n);
    }

    static size_t getAllocatedBytes() { return counter;}

    countingAllocator() throw(): std::allocator<T>() {}
    countingAllocator(const countingAllocator &a) throw(): std::allocator<T>(a) { }
    template <class U>                    
    countingAllocator(const countingAllocator<U> &a) throw(): std::allocator<T>(a) { }
    ~countingAllocator() throw() { }
};

template <int N>
struct big_struct {
    char c[N];
};

template<

    class Key,
    class T,
    class Hash = std::hash<Key>,
    class KeyEqual = std::equal_to<Key>,
    class Allocator = std::allocator< std::pair<const Key, T> >
> class unordered_map;

int main( int argc, char ** argv) {
    typedef big_struct<64> data_type;
    typedef std::unordered_map<int, data_type*, std::hash<int>, std::equal_to<int>, 
                            countingAllocator< std::pair< const int, data_type*>> > map_type;

    map_type m;

    for (int i = 0; i < 1000 * 1000; i++) {
            m.insert(std::make_pair(i, new data_type));
    }   

    for (map_type::iterator it = m.begin(); it != m.end();) {
            delete it->second;
            it = m.erase(it);
    }   

    std::cout << "allocated memory before returning " << countingAllocator< std::pair< const int, data_type*>> ::getAllocatedBytes() << std::endl;

    return 0;
}

程序的输出结果:

allocated memory before returning 1056323

基本上这意味着你需要以某种方式调用地图析构函数,以正确地清除先前分配的内存,并且你可以通过以下几种方式实现:
  • 将unordered_map放入shared_ptr
  • 使unordered_map超出作用域

我在我的PublicProfileTests存储库上上传了配置文件代码,以便您可以进行贡献。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接