簡體   English   中英

用於std :: list和std :: map的Visual C ++ 11堆棧分配器

[英]Visual C++11 stack allocator for std::list and std::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存儲到預分配的內存中,以及一些其他成員變量。

那里的問題是我需要在MemPool類中對我的MemPoolStack進行一些引用,以便Visual C ++ 11映射或列表可以構造我的分配器的所有不同方式都以每個列表或映射一個MemPoolStack實例結束。 然后我可以在MemPool::allocate()使用MemPoolStack::Pop()和在MemPool::deallocate() MemPoolStack::Push() MemPool::deallocate()

我還需要一種方法來初始構造我的分配器,指定大小。 我嘗試將shared_ptr<MemPoolStack>放入MemPool但是當列表決定調用分配器的默認構造函數時,它最終迷路了。

我也樂於拋棄所有這些代碼,以找到替代原始問題的好方法。

由於您只需要一個基礎池,並且分配器可以復制並重新綁定,因此您不能將狀態直接存儲在分配器中。

可以做的是存儲指向狀態的指針(或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));

將使用指定的后備池。

好的,一旦我的腦細胞被激發到無用的狀態,我就開始工作了。

這是分配器的代碼(我在這里省略了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()中設置斷點MemPool::allocate()顯示現在始終填充Stack成員。

太好了,C ++ 11的萬歲!

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM