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

时间:2013-12-18 13:16:38

标签: c++ c++11 stl allocator visual-c++-2012

我想提高list和map的特定用法的性能,其中项目的数量具有100000的硬限制。在这种情况下,STL默认分配器显然不是最佳选择,因为清理所有数千个小物体需要很长时间(> 10秒!)。更不用说所有其他潜在问题了。

显然,为了改进这一点,我可以预先分配正确的内存量以包含所有列表/映射节点。到目前为止,我已经能够实现默认分配器的工作版本(通过从std :: allocator_traits派生),它为每个节点使用alloc / free。但我正在努力找出如何修改它以允许“有状态”使用,例如,我非常简单的堆栈:

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

我正在实例化我的列表和地图:

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

MemPoolStack本身并不是真正的问题,它可能有错误,但它仅仅是出于示例目的。关键是MemPoolStack类将unique_ptr存储到预分配的内存和其他一些成员变量。

问题是我需要对MemPoolStack类中的MemPool进行一些引用,以便Visual C ++ 11映射或列表构建分配器的所有不同方式最终每个列表或地图都有一个MemPoolStack个实例。然后我可以使用MemPoolStack::Pop()中的MemPool::allocate()MemPoolStack::Push()中的MemPool::deallocate()

我还需要一种方法来初始构建我的分配器,指定大小。我尝试在shared_ptr<MemPoolStack>中放置一个MemPool,但当列表决定调用分配器的默认构造函数时,它最终会丢失...

我也愿意抛弃所有这些代码,以便为原始问题提供一个很好的替代解决方案。

2 个答案:

答案 0 :(得分:3)

由于您需要单个基础池,并且可以复制和重新绑定分配器,因此无法将状态直接存储在分配器中。

可以做的是将指针(或shared_ptr)存储到您的状态,以便分配器的副本浅层复制指针,引用相同的基础池。

请注意,您需要为分配器编写默认构造函数,并让它创建新的后备池,或者需要创建具有特定后备池的分配器实例并将其传递给容器构造函数。

所以这个:

list<TKey, MemPool<TKey>> Keys;

将默认构造一个分配器(类似于MemPool<list<TKey>::node>),并且该分配器实例必须创建自己的支持状态;虽然这个:

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

将通过您必须提供的select_on_container_copy_construction() const方法复制原始分配器实例(因此您可以使两个容器与其单独的分配器实例共享同一个池);最后这个:

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

将使用指定的后备池。

答案 1 :(得分:1)

好的,所以当我的大脑细胞由于无用而开始行动时,我已经开始工作了。

这是分配器的代码(我在这里省略了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;}

但是,我对这一切的实例化现在更加冗长:

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.

MemPool::allocate()中设置断点显示现在始终填充Stack成员。

很好,很好用于C ++ 11!