具有固定插入次数的Map的内存分配

时间:2010-03-27 05:59:48

标签: c++ memory stl map allocation

我想在地图中插入n个元素,其中n是提前知道的。我不想在每次插入时分配内存。我想在开始时分配所有内存。有没有办法做到这一点?如果是这样,怎么样?写一些内存分配器会有帮助吗?

我运行了GMan的代码并获得了以下输出。通过调用“new”打印GetMem,并从调用delete打印FreeMem。 size是请求的字节数,ptr是返回的指针。显然,在插入期间正在进行分配/解除分配。你怎么解释这个?

GetMem size 40,ptr 0x8420008
GetMem size 40,ptr 0x8420038
GetMem大小120,ptr 0x8420068
GetMem大小120,ptr 0x84200e8
FreeMem ptr 0x8420068
FreeMem ptr 0x8420038
FreeMem ptr 0x8420008
插入:[0,0]
GetMem size 40,ptr 0x8420008
FreeMem ptr 0x8420008
插入:[1,2]
GetMem size 40,ptr 0x8420008
FreeMem ptr 0x8420008
插入:[2,4]
GetMem size 40,ptr 0x8420008
FreeMem ptr 0x8420008
插入:[3,6]
GetMem size 40,ptr 0x8420008
FreeMem ptr 0x8420008
插入:[4,8]
GetMem size 40,ptr 0x8420008
FreeMem ptr 0x8420008
插入:[5,10]
GetMem size 40,ptr 0x8420008
FreeMem ptr 0x8420008
GetMem size 40,ptr 0x8420008
FreeMem ptr 0x8420008
GetMem size 40,ptr 0x8420008
FreeMem ptr 0x8420008
GetMem size 40,ptr 0x8420008
FreeMem ptr 0x8420008
GetMem size 40,ptr 0x8420008
FreeMem ptr 0x8420008
FreeMem ptr 0x84200e8
St9bad_alloc

4 个答案:

答案 0 :(得分:3)

对分配测试的响应

将此添加到我在下面提供的任何一个样本中:

#include <cstdlib>

void* allocate(size_t pAmount)
{
    std::cout << "Allocating " << pAmount << " bytes." << std::endl;

    return std::malloc(pAmount);
}

void deallocate(void* pPtr)
{
    std::cout << "Deallocating." << std::endl;

    std::free(pPtr);
}

void* operator new(size_t pAmount) // throw(std::bad_alloc)
{
    return allocate(pAmount);
}

void *operator new[](size_t pAmount) // throw(std::bad_alloc)
{
    return allocate(pAmount);
}

void *operator new(size_t pAmount, const std::nothrow_t&) throw()
{
    return allocate(pAmount);
}

void *operator new[](size_t pAmount, const std::nothrow_t&) throw()
{
    return allocate(pAmount);
}

void operator delete(void* pMemory) throw()
{
    deallocate(pMemory);
}

void operator delete[](void* pMemory) throw()
{
    deallocate(pMemory);
}

void operator delete(void* pMemory, const std::nothrow_t&) throw()
{
    deallocate(pMemory);
}

void operator delete[](void* pMemory, const std::nothrow_t&) throw()
{
    deallocate(pMemory);
}

(注意,这些不是分配运算符的完全正确的替换,并且用于演示。)

运行运行时大小的示例程序给出了以下输出:

  

分配40个字节   分配40个字节   分配40个字节   分配40个字节   分配40个字节   分配40个字节   分配40个字节   重新分配。
  重新分配。
  分配120个字节   重新分配。
  分配20个字节   重新分配。
  分配40个字节   重新分配。
  重新分配。
  重新分配。
  插入:[0,0]
  插入:[1,2]
  插入:[2,4]
  插入:[3,6]
  插入:[4,8]
  重新分配。
  重新分配。
  重新分配。
  分配不好

注意插入开始后没有分配。你应该得到没有内存分配。你是如何产生输出的?


分配器

你需要的是一个新的分配器。这是我现在制作的东西,所以它相对未经测试,但对我来说看起来不错。

它会创建一个freelist并使用它来释放内存。分配器的构造需要O(N),但分配和解除分配都是O(1)。 (非常快!)此外,一旦构建完成,就不会再进行内存分配。虽然freelists具有平均位置,但它可能比默认分配器通常会从地图中获得的那样。

这是:

#include <cassert>
#include <exception>
#include <limits>
#include <vector>

// gets rid of "unused parameter" warnings
#define USE(x) (void)(x)

template <typename T, size_t N>
class freelist_allocator
{
public: 
    // types
    typedef T value_type;
    typedef const T const_value_type;

    typedef value_type& reference;
    typedef const_value_type& const_reference;

    typedef value_type* pointer;
    typedef const_value_type* const_pointer;

    typedef std::size_t size_type;
    typedef std::ptrdiff_t difference_type;

    // ensure it can hold both a pointer and T
    struct block_type
    {
        char data[sizeof(T) > sizeof(void*) ?
                    sizeof(T) : sizeof(void*)];
    };

    typedef std::vector<block_type> buffer_type;

    // constants
    static const size_t Elements = N;

    // rebind
    template<typename U, size_t M = Elements>
    struct rebind
    {
        typedef freelist_allocator<U, M> other;
    };

    // constructor
    freelist_allocator(void) :
    mBuffer(Elements),
    mNext(0)
    {
        build_list();
    }

    freelist_allocator(const freelist_allocator&) :
    mBuffer(Elements),
    mNext(0)
    {
        build_list();
    }

    template<typename U, size_t M>
    freelist_allocator(const freelist_allocator<U, M>&) :
    mBuffer(Elements),
    mNext(0)
    {
        build_list();
    }

    // address
    pointer address(reference r)
    {
        return &r;
    }

    const_pointer address(const_reference r)
    {
        return &r;
    }

    // memory
    pointer allocate(size_type pCount, const void* = 0)
    {
        USE(pCount); // pCount unused when assert is disabled in release
        assert(pCount == 1 && "freelist_allocator is noncontiguous");

        // end of list
        if (!mNext)
            throw std::bad_alloc();

        // remove from list
        void* memory = mNext;
        mNext = data_as_ptr(*mNext);

        return static_cast<pointer>(memory);
    }

    void deallocate(pointer pPtr, size_type)
    {
        // get back our block
        block_type* b = reinterpret_cast<block_type*>(pPtr);

        // reinsert to list
        data_as_ptr(*b) = mNext;
        mNext = b;
    }

    // size
    size_type max_size(void) const
    {
        static const size_t MaxSize = 
                    std::numeric_limits<size_type>::max();
        return MaxSize / sizeof(value_type);
    }

    // construction / destruction
    void construct(pointer pPtr, const T& pT)
    {
        new (pPtr) T(pT);
    }

    void destroy(pointer pPtr)
    {
        USE(pPtr); // trivial destructor skips next line
        pPtr->~value_type();
    }   

private:
    // utility
    block_type*& data_as_ptr(block_type& pBlock)
    {
        return reinterpret_cast<block_type*&>(pBlock.data);
    }

    void build_list(void)
    {
        // build list
        for (size_t i = 1; i < mBuffer.size(); ++i)
        {
            data_as_ptr(mBuffer[i - 1]) = &mBuffer[i];
        }

        mNext = &mBuffer[0];
    }

    // members
    buffer_type mBuffer;
    block_type* mNext;
};

// equality
template<typename T, size_t N>
bool operator==(freelist_allocator<T, N> const&,
                freelist_allocator<T, N> const&)
{
    return false;
}

template<typename T, size_t N>
bool operator!=(freelist_allocator<T, N> const& pX,
                freelist_allocator<T, N> const& pY)
{
    return !(pX == pY);
}

#undef USE

并使用:

#include <iostream>
#include <map>
#include <string>

static const size_t MaxElements = 5;

typedef std::pair<int, int> pair_type;
typedef freelist_allocator<pair_type, MaxElements> allocator_type;
typedef std::map<int, int,
                    std::less<int>, allocator_type> map_type;

void do_insert(int pX, int pY, map_type& pMap)
{
    std::cout << "Inserting: [" << pX << ","
        << pY << "]" << std::endl;

    pMap.insert(std::make_pair(pX, pY));
}

int main(void)
{   
    try
    {
        map_type m;

        // just keep inserting
        for (int i = 0; i <= std::numeric_limits<int>::max() / 2; ++i)
        {
            // will throw at MaxElements insertions
            do_insert(i, i * 2, m);
        }
    }
    catch (const std::exception& e)
    {
        std::cerr << e.what() << std::endl;
    }
}

现在我已经把这个大小编成了一个编译时常量,但你想要一个运行时版本让我知道,我会把它写出来。这是一个版本需要一个运行时的大小:

#include <cassert>
#include <exception>
#include <limits>
#include <vector>

// gets rid of "unused parameter" warnings
#define USE(x) (void)(x)

template <typename T>
class freelist_allocator
{
public: 
    // types
    typedef T value_type;
    typedef const T const_value_type;

    typedef value_type& reference;
    typedef const_value_type& const_reference;

    typedef value_type* pointer;
    typedef const_value_type* const_pointer;

    typedef std::size_t size_type;
    typedef std::ptrdiff_t difference_type;

    // ensure it can hold both a pointer and T
    struct block_type
    {
        char data[sizeof(T) > sizeof(void*) ?
                    sizeof(T) : sizeof(void*)];
    };

    typedef std::vector<block_type> buffer_type;

    // rebind
    template<typename U>
    struct rebind
    {
        typedef freelist_allocator<U> other;
    };

    // constructor
    freelist_allocator(size_t pElements) :
    mBuffer(pElements),
    mNext(0)
    {
        build_list();
    }

    freelist_allocator(const freelist_allocator& pRhs) :
    mBuffer(pRhs.size()),
    mNext(0)
    {
        build_list();
    }

    template<typename U>
    freelist_allocator(const freelist_allocator<U>& pRhs) :
    mBuffer(pRhs.size()),
    mNext(0)
    {
        build_list();
    }

    // address
    pointer address(reference r)
    {
        return &r;
    }

    const_pointer address(const_reference r)
    {
        return &r;
    }

    // memory
    pointer allocate(size_type pCount, const void* = 0)
    {
        USE(pCount); // pCount unused when assert is disabled in release
        assert(pCount == 1 && "freelist_allocator is noncontiguous");

        // end of list
        if (!mNext)
            throw std::bad_alloc();

        // remove from list
        void* memory = mNext;
        mNext = data_as_ptr(*mNext);

        return static_cast<pointer>(memory);
    }

    void deallocate(pointer pPtr, size_type)
    {
        // get back our block
        block_type* b = reinterpret_cast<block_type*>(pPtr);

        // reinsert to list
        data_as_ptr(*b) = mNext;
        mNext = b;
    }

    // size
    size_type max_size(void) const
    {
        static const size_t MaxSize = 
                    std::numeric_limits<size_type>::max();
        return MaxSize / sizeof(value_type);
    }

    size_t size(void) const
    {
        return mBuffer.size();
    }

    // construction / destruction
    void construct(pointer pPtr, const T& pT)
    {
        new (pPtr) T(pT);
    }

    void destroy(pointer pPtr)
    {
        USE(pPtr); // trivial destructor skips next line
        pPtr->~value_type();
    }   

private:
    // utility
    block_type*& data_as_ptr(block_type& pBlock)
    {
        return reinterpret_cast<block_type*&>(pBlock.data);
    }

    void build_list(void)
    {
        // build list
        for (size_t i = 1; i < mBuffer.size(); ++i)
        {
            data_as_ptr(mBuffer[i - 1]) = &mBuffer[i];
        }

        mNext = &mBuffer[0];
    }

    // members
    buffer_type mBuffer;
    block_type* mNext;
};

// equality
template<typename T>
bool operator==(freelist_allocator<T> const&,
                freelist_allocator<T> const&)
{
    return false;
}

template<typename T, size_t N>
bool operator!=(freelist_allocator<T> const& pX,
                freelist_allocator<T> const& pY)
{
    return !(pX == pY);
}

#undef USE

使用:

#include <iostream>
#include <map>
#include <string>

static const size_t MaxElements = 5;

template <typename Key, typename Value>
struct map_types // helpful
{
    typedef std::pair<Key, Value> pair_type;
    typedef freelist_allocator<pair_type> allocator_type;
    typedef std::less<Key> predicate_type;
    typedef std::map<Key, Value,
                    predicate_type, allocator_type> map_type;
};

template <typename Key, typename Value, typename Map>
void do_insert(const Key& pX, const Value& pY, Map& pMap)
{
    std::cout << "Inserting: [" << pX << ","
                << pY << "]" << std::endl;

    pMap.insert(std::make_pair(pX, pY));
}

int main(void)
{   
    try
    {
        typedef map_types<int, int> map_types;

        // double parenthesis to avoid vexing parse
        map_types::map_type m((map_types::predicate_type()),
                            map_types::allocator_type(MaxElements));

        // just keep inserting
        for (int i = 0; i <= std::numeric_limits<int>::max() / 2; ++i)
        {
            do_insert(i, i * 2, m);
        }
    }
    catch (const std::exception& e)
    {
        std::cerr << e.what() << std::endl;
    }
}

如果没有剩余的插槽,运行时版本可能会分配更多空间,这可能很有用。但我把它留给你。 (不要调整向量的大小!你可能会丢失整个缓冲区。可能需要list个向量。)

注意,如果你可以使用Boost,他们在Pool library中有这样的分配器。也就是说,他们的分配器跟踪您请求内存的顺序,以确保反向构造销毁顺序。这将分配和解除分配转换为O(N)。 (在我看来这是一个可怕的选择。)我实际上在boost::pool<>周围编写了我自己的分配器来解决这个问题。

答案 1 :(得分:1)

对于map vector所需的方式,map不需要这样做。由于vector在内部实现为树,因此插入很便宜(您不会在整个块周围移动)。另一方面,对于new插入,它将当前保留的绑定需要移动所有先前分配的元素。

也就是说,如果分配性能对您来说至关重要,那么可以编写一个自定义分配器,例如,从预先分配的缓冲区分配。如果您正确实现此功能,它将比map使用的默认{{1}}更快。但是,我怀疑你必须走这么远。

答案 2 :(得分:1)

使用哈希表。 unordered_map可能不合适,因为它允许单独分配每个对象,并使用桶的“封闭寻址”而不是一个平坦的内存块。

有关您应该考虑的结构类型的说明,请参阅http://en.wikipedia.org/wiki/Hash_table#Open_addressing。实现具有恒定访问时间和恒定插入时间的关联容器并不太困难。

但是,对删除的支持可能有点混乱,你需要在哈希表中分配相当大的空白空间,或许你实际使用的空间加倍,以保持地址空间整洁。

答案 3 :(得分:0)

std::map没有为此提供任何内置支持。但是,如果元素的数量足够小,那么您可以创建std::vector个对并使用vector::reserve方法来保留所需的空间。