3

多数の要素を追加してから、boost::unordered_mapでそれらをすべて削除しました。次に、このプログラムが保持するメモリが198MB((64 + 4)* 2Mより大きい)であり、unordered_mapサイズが0であることがわかりました。

次に、ベクトルをテストしますが、そのような問題はありません。なんで?

#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;
}
4

2 に答える 2

9

言語ランタイムは、再度使用する可能性があると想定して、割り当てられたメモリを保持します。何百万もの小さなブロックをOSに戻すにはかなりの時間がかかり、プログラムの実行が遅くなります。

非常に大きなベクトルがある場合でも、それは単一のメモリブロックにすぎません。一部のコンパイラは、この種のメモリが不要になったときに返すことを検討しています。1つの大きなブロックを返すことは、100万の小さなブロックよりもかなり効率的です。

于 2012-05-31T07:32:55.470 に答える
1

これは、ブーストがC++の標準部分になったときに何が起こるかを示す明確な例の1つです。

std::unordered_map

はメモリの解放を事実上制御できず、上記の答えは正しくありません

Astd::unordered_mapは、発生するまでメモリを解放しない場合rehashがあります。再ハッシュが発生する可能性がある場合は、ドキュメントを見るsize()と要素の数であると記載されています。実際のサイズのアイデアが必要な場合は、マップ内にカスタムアロケータを追加し、割り当て/割り当て解除されたバイトをカウントする必要があります。

動作が文書化されていないため(それを制御するAPIがないため)、実装がメモリを解放するかどうか(しばらくマップを使用しない場合に適しています)、または実装キャッシュかどうかがわからないため、これは残念です。メモリ(別の要素を再度挿入する場合に適しています)。

これにより、メモリ効率の高い方法でunordered_mapを使用することが非常に困難になります。また、これは基本的に、文書化されることなく大量のメモリ使用の余地を残します(要素のないマップが数百メガバイトかかる可能性があることはどこにも述べられていません)

これは、メモリ使用量をプロファイルするカスタムアロケータです

#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リポジトリにプロファイルコードをアップロードしました

于 2015-06-23T08:09:16.073 に答える