简体   繁体   English

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

[英]Memory Allocation for a Map with a fixed number of insertions

I want to insert n elements into a map where n is known ahead of time. 我想在地图中插入n个元素,其中n是提前知道的。 I do not want memory allocation at each insertion. 我不想在每次插入时分配内存。 I want all memory allocation at the beginning. 我想在开始时分配所有内存。 Is there a way to do this? 有没有办法做到这一点? If so, how? 如果是这样,怎么样? Will writing some sort of memory allocator help? 写一些内存分配器会有帮助吗?

I ran GMan's code and got the following output. 我运行了GMan的代码并获得了以下输出。 GetMem is printed from a call to "new" and FreeMem is printed from a call to delete. 通过调用“new”打印GetMem,并从调用delete打印FreeMem。 size is the number of bytes requested and ptr is the pointer returned. size是请求的字节数,ptr是返回的指针。 Clearly, allocation/deallocation is going on during insertion. 显然,在插入期间正在进行分配/解除分配。 How do you explain this? 你怎么解释这个?

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

Response to allocation test 对分配测试的响应

Add this to either of the samples I give below: 将此添加到我在下面给出的任一样本中:

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

(Note, these are not fully correct replacements of the allocation operators, and are for demonstration.) (注意,这些不是完全正确的分配操作符的替换,并且用于演示。)

Running the runtime-sized sample program gives me the following output: 运行运行时大小的示例程序给出了以下输出:

Allocating 40 bytes. 分配40个字节。
Allocating 40 bytes. 分配40个字节。
Allocating 40 bytes. 分配40个字节。
Allocating 40 bytes. 分配40个字节。
Allocating 40 bytes. 分配40个字节。
Allocating 40 bytes. 分配40个字节。
Allocating 40 bytes. 分配40个字节。
Deallocating. 取消分配。
Deallocating. 取消分配。
Allocating 120 bytes. 分配120个字节。
Deallocating. 取消分配。
Allocating 20 bytes. 分配20个字节。
Deallocating. 取消分配。
Allocating 40 bytes. 分配40个字节。
Deallocating. 取消分配。
Deallocating. 取消分配。
Deallocating. 取消分配。
Inserting: [0,0] 插入:[0,0]
Inserting: [1,2] 插入:[1,2]
Inserting: [2,4] 插入:[2,4]
Inserting: [3,6] 插入:[3,6]
Inserting: [4,8] 插入:[4,8]
Deallocating. 取消分配。
Deallocating. 取消分配。
Deallocating. 取消分配。
bad allocation 分配不好

Note there are no allocations once insertion has begun. 请注意,插入开始后没有分配。 You should be getting no memory allocations. 你应该没有内存分配。 How are you generating your output? 你是如何产生输出的?


The allocators 分配器

What you need is a new allocator. 你需要的是一个新的分配器。 Here's something I made now, so it's relatively untested, but it looks good to me. 这是我现在制作的东西,所以它相对未经测试,但对我来说看起来不错。

It creates a freelist and uses that to give out memory. 它创建了一个空闲列表并使用它来释放内存。 Construction of the allocator takes O(N), but both allocations and deallocations are O(1). 分配器的构造需要O(N),但分配和解除分配都是O(1)。 (Very fast!) Also, once it's constructed, no more memory allocations take place. (非常快!)此外,一旦构建完成,就不会再进行内存分配。 Though freelists have average locality, it probably beats what you'd normally get out of a map with the default allocator. 尽管freelists具有平均位置,但它可能比默认分配器通常会从地图中获得的那样。

Here it is: 这里是:

#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

And 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;
    }
}

For now I've made the size a compile-time constant, but you want a run-time version just let me know and I'll write that up. 现在我已经把这个大小编成了一个编译时常量,但你想要一个运行时版本让我知道,我会写出来。 Here's a version that takes a size at run-time: 这是一个在运行时占用大小的版本:

#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

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

The run-time version could possibly allocate more space if there are no more remaining slots, which can be useful. 如果没有剩余的插槽,运行时版本可能会分配更多空间,这可能很有用。 But I leave that to you. 但我把它留给你。 (Don't resize the vector! You'll possibly lose your entire buffer. You'll need a list of vectors, likely.) (不要调整向量的大小!你可能会丢失整个缓冲区。你可能需要一个向量list 。)

Note if you can use Boost, they have such an allocator in their Pool library . 注意,如果你可以使用Boost,他们的Pool库中就有这样的分配器。 That said, their allocator keeps track of the order you request memory, to ensure reverse construction destruction order. 也就是说,他们的分配器跟踪您请求内存的顺序,以确保反向构造销毁顺序。 This turns allocations and deallocations into O(N). 这将分配和解除分配转换为O(N)。 (A terrible choice in my opinion.) I actually wrote my own allocator around boost::pool<> to get around this. (在我看来,这是一个可怕的选择。)我实际上是在boost::pool<>周围编写了自己的分配器来解决这个问题。

This isn't required for a map the way it's required for a vector . map不需要vector所需的方式。 Since map is implemented as a tree internally, insertions are cheap (you don't move around whole chunks). 由于map在内部实现为树,因此插入很便宜(您不会在整个块周围移动)。 On the other hand, for a vector insertions that take it over the currently reserved bound require to move all the previously allocated elements. 另一方面,对于将其带过当前保留边界的vector插入,需要移动所有先前分配的元素。

That said, if allocation performance is super crucial to you, you can write a custom allocator that, say, allocates from a pre-allocated buffer. 也就是说,如果分配性能对您来说至关重要,您可以编写一个自定义分配器,例如,从预分配的缓冲区分配。 If you implement this correctly, it will be faster than the default new used by map . 如果正确实现它,它将比map使用的默认new更快。 However, I doubt you must go this far. 但是,我怀疑你必须走到这一步。

Use a hashtable. 使用哈希表。 unordered_map might be inappropriate as it allows each object to be allocated separately, and uses "closed addressing" with buckets instead of one flat block of memory. unordered_map可能不合适,因为它允许单独分配每个对象,并使用带有桶的“封闭寻址”而不是一个平坦的内存块。

See http://en.wikipedia.org/wiki/Hash_table#Open_addressing for a description of the kind of structure you should consider. 有关您应该考虑的结构类型的说明,请参见http://en.wikipedia.org/wiki/Hash_table#Open_addressing It's not too difficult to implement an associative container with constant access time and constant insertion time. 实现具有恒定访问时间和恒定插入时间的关联容器并不困难。

Support for deletion can be a little messy, though, and you will need to allocate considerable empty space in the hash table, perhaps double what you actually use, to keep the address space uncluttered. 但是,对删除的支持可能有点混乱,并且您需要在哈希表中分配相当大的空白空间,可能是实际使用的两倍,以保持地址空间整洁。

std::map doesn't provide any built in support for doing this. std::map不提供任何内置支持。 But, if the number of elements is small enough, then you can create std::vector of pairs and use vector::reserve method to reserve the needed space. 但是,如果元素的数量足够小,那么您可以创建std::vector of pairs并使用vector::reserve方法来保留所需的空间。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM