简体   繁体   English

用于std :: list和std :: map的Visual C ++ 11堆栈分配器

[英]Visual C++11 stack allocator for std::list and std::map

I'd like to increase the performance of a specific usage of list and map, where the number of items has a hard limit in the order of 100000. The STL default allocator obviously isn't the best choice in this situation, since cleaning up all the thousands of small objects takes a very long time (>10sec!). 我想提高列表和映射的特定用法的性能,其中项的数量具有100000的硬限制。在这种情况下,STL默认分配器显然不是最佳选择,因为清理所有成千上万的小物体都需要很长时间(> 10秒!)。 Not to mention all the other potential issues. 更不用说所有其他潜在的问题。

So obviously to improve this I can preallocate the correct amount of memory to contain all of the list/map nodes. 因此很明显,为了改善这一点,我可以预分配正确的内存量以包含所有列表/映射节点。 So far I've been able to implement a working version of the default allocator (by deriving from std::allocator_traits), that uses alloc/free for each node. 到目前为止,我已经能够实现默认分配器的工作版本(通过从std :: allocator_traits派生),该版本为每个节点使用alloc / free。 But I'm struggling to find out how to modify this to allow for the "stateful" use of, for example, my very simple stack: 但是我正在努力寻找如何修改它以允许“状态化”使用(例如)我非常简单的堆栈:

using namespace std;
class MemPoolStack
{
public:
    size_t Size;
    size_t Mult;
    size_t Total;
    size_t Top;
    size_t Last;
    unique_ptr<byte[]> Data;
    unique_ptr<size_t[]> Nexts;

    MemPoolStack(size_t size, size_t mult) :
        Size(size),
        Mult(mult),
        Total(size * mult),
        Top(0),
        Last(0),
        Data(new byte[Total]),
        Nexts(new size_t[Size])
    {
    }
    size_t& Next(size_t i)
    {
        return *(Nexts.get() + i);
    }
    void* Pop()
    {
        byte* p = nullptr;
        if(Top<Size)
        {
            p = Data.get() + (Top * Mult);
            bool last = (Top==Last);
            size_t next = last ? Top+1 : Next(Top);
            if(last) Next(Top) = next;
            Top = next;
            if(Top>Last) Last=Top;
        }
        else
        {
            p = nullptr;
        }
        return p;
    }
    bool Push(void* p)
    {
        ptrdiff_t diff = (byte*)p - Data.get();
        size_t index = ((size_t)diff / Mult);
        if(diff>=0 && index<Size)
        {
            Next(index) = Top;
            Top = index;
            return true;
        }
        return false;
    }
};

template <class T> struct MemPool
{
    typedef T value_type;
    MemPool() throw() {}
    template <class U> MemPool (const MemPool<U>&) throw() {}
    template <class U> struct rebind { typedef MemPool<U> other; }; //off-topic: why doesn't allocator_traits define this?
    T* allocate (size_t n) 
    {
        return static_cast<T*>(malloc(n*sizeof(T))); 
    }
    void deallocate (T* p, size_t n) 
    { 
        free(p); 
    }
};

template <class T, class U>
bool operator== (const MemPool<T>&, const MemPool<U>&) throw()
{return true;}

template <class T, class U>
bool operator!= (const MemPool<T>&, const MemPool<U>&) throw()
{return false;}

And I'm instantiating my list and map like this: 我正在实例化我的列表和地图,如下所示:

list<TKey, MemPool<TKey>> Keys;
map<TKey, MapType, less<TKey>, MemPool<MapType>> Map;

The MemPoolStack itself isn't really the issue here, it probably has bugs but it's just for example purposes. MemPoolStack本身并不是真正的问题,它可能存在错误,但这只是出于示例目的。 The point is that the MemPoolStack class stores a unique_ptr to the preallocated memory, and some other member variables. 关键是MemPoolStack类将一个unique_ptr存储到预分配的内存中,以及一些其他成员变量。

The problem there is that I need to have some reference to my MemPoolStack in the MemPool class, so that all the different ways that a Visual C++11 map or list can construct my allocator all end up with one MemPoolStack instance per list or map. 那里的问题是我需要在MemPool类中对我的MemPoolStack进行一些引用,以便Visual C ++ 11映射或列表可以构造我的分配器的所有不同方式都以每个列表或映射一个MemPoolStack实例结束。 Then I could use MemPoolStack::Pop() in MemPool::allocate() , and MemPoolStack::Push() in MemPool::deallocate() . 然后我可以在MemPool::allocate()使用MemPoolStack::Pop()和在MemPool::deallocate() MemPoolStack::Push() MemPool::deallocate()

I also need a way to initially construct my allocator, specifying the size. 我还需要一种方法来初始构造我的分配器,指定大小。 I tried putting a shared_ptr<MemPoolStack> in MemPool but it ended up getting lost when the list decided to call the allocator's default constructor... 我尝试将shared_ptr<MemPoolStack>放入MemPool但是当列表决定调用分配器的默认构造函数时,它最终迷路了。

I'm also open to throwing away all of this code for a good alternative solution to the original problem. 我也乐于抛弃所有这些代码,以找到替代原始问题的好方法。

Since you want a single underlying pool, and allocators can be copied and re-bound, you can't store your state directly in the allocator. 由于您只需要一个基础池,并且分配器可以复制并重新绑定,因此您不能将状态直接存储在分配器中。

What you can do is store a pointer (or a shared_ptr ) to your state, such that copies of your allocator shallow-copy the pointer, referring to the same underlying pool. 可以做的是存储指向状态的指针(或shared_ptr ),以便分配器的副本将指针复制到浅表中,并指向同一基础池。

Note that you either need to write a default constructor for your allocator, and have it create a new backing pool, or you need to create an allocator instance with a specific backing pool and pass it to the container constructor. 请注意,您要么需要为分配器编写一个默认的构造函数,然后让它创建一个新的后备池,要么需要创建一个具有特定后备池的分配器实例并将其传递给容器构造函数。

So this: 所以这:

list<TKey, MemPool<TKey>> Keys;

will default construct an allocator (which will be something like MemPool<list<TKey>::node> ), and that allocator instance will have to create its own backing state; 将默认构造一个分配器(类似于MemPool<list<TKey>::node> ),并且该分配器实例将必须创建自己的后备状态; while this: 而这:

list<TKey, MemPool<TKey>> MoreKeys(Keys);

will copy that original allocator instance via a select_on_container_copy_construction() const method you must provide (so you can make both containers, with their separate allocator instances, share the same pool); 将通过必须提供的select_on_container_copy_construction() const方法复制该原始分配器实例(这样,您可以使两个容器及其各自的分配器实例共享同一池); and finally this: 最后是这样的:

map<TKey, MapType, less<TKey>, MemPool<MapType>> Map(MemPool<MapType>(my_pool));

will use the specified backing pool. 将使用指定的后备池。

OK, so I've gotten this working, once my brain cells were fired into action thanks to Useless. 好的,一旦我的脑细胞被激发到无用的状态,我就开始工作了。

Here's the code of the allocator (I've omitted the MemPoolStack here since it hasn't changed and probably is broken anyway, that's my next task - but the issue here was to get a working stateful allocator): 这是分配器的代码(我在这里省略了MemPoolStack ,因为它没有发生变化,而且很可能已经损坏了,这是我的下一个任务-但这里的问题是要获得一个有状态的有效分配器):

template <class T> struct MemPool
{
    typedef T value_type;
    shared_ptr<MemPoolStack> Stack; //My allocator's state goes here!
    template <class U> MemPool (const MemPool<U>& p) throw()
    {
        if(p.Stack->Mult!=sizeof(U))
        {
            throw runtime_error("Can't rebind MemPool to another size object. Sorry.");
        }
        Stack = p.Stack; //interestingly, this constructor is used by std::map but not std::list
    }
    template <class U> struct rebind { typedef MemPool<U> other; }; //off-topic: maybe they fixed this one in VS2019?
    MemPool(size_t count) :
        Stack(new MemPoolStack(count, sizeof(T))) //I can allocate the memory here!
    {
    }
    T* allocate (size_t n) 
    {
        //Now I can return Stack->Pop() here instead of using malloc!
        if(n!=1) throw runtime_error("MemPool can only allocate one item at a time. Sorry.");
        return static_cast<T*>(Stack->Pop());
        //return static_cast<T*>(malloc(n*sizeof(T)));  
    }
    void deallocate (T* p, size_t n) 
    { 
        ///Can use Stack->Push() here instead of free!
        if(n!=1) throw runtime_error("MemPool can only deallocate one item at a time. Sorry.");
        Stack->Push(static_cast<void*>(p));
        //free(p);
    }
};

template <class T, class U>
bool operator== (const MemPool<T>&, const MemPool<U>&) throw()
{return true;}

template <class T, class U>
bool operator!= (const MemPool<T>&, const MemPool<U>&) throw()
{return false;}

However, my instantiation of all this is now a little more long-winded: 但是,现在我对所有这些的实例都有些冗长:

typedef pair<size_t, typename list<TKey>::iterator> MapType;
typedef MemPool<_Tree_node<pair<TKey,MapType>,void*>> MapPoolType;
typedef MemPool<_List_node<TKey,void*>> ListPoolType;

list<TKey, ListPoolType> Keys(ListPoolType(capacity+10));
map<TKey, MapType, less<TKey>, MapPoolType> Map(MapPoolType(capacity+10));
//I use list/map capacity+10 because the containers like a few free nodes to themselves.
//Probably should investigate further as to what these numbers actually need to be.

Setting a breakpoint in MemPool::allocate() shows that the Stack member is now always populated. MemPool::allocate()中设置断点MemPool::allocate()显示现在始终填充Stack成员。

Great, Hooray for C++11! 太好了,C ++ 11的万岁!

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

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