1. 程式人生 > >C++標準模板庫中list大資料量情況下析構效率的研究

C++標準模板庫中list大資料量情況下析構效率的研究

list在程式設計中是一種十分常用的序列式容器,如果你的程式更注重容器以下特性時,list可謂首選容器:

1、資料按原本順序儲存(不需要排序)

2、容器可以高效在任意位置插入、刪除資料

3、迭代器不會因插入與刪除等操作而失效(當然被刪除元素的迭代器除外)

4、不需要隨機訪問

對於記憶體的分配,vector、deque等是預先分配,當分配的記憶體不夠時重新分配調整,是一種統一分配統一釋放的機制

而list則是每插入或刪除一個元素則分配或釋放一塊ListNode記憶體,是一種逐個分配逐個釋放的機制

兩種不同的機制導致了當list中擁有大量資料元素時,執行clear()操作或析構物件的效率偏低

效率上有多少差異呢,請考慮下面一個對比list與vector的測試:

#include <Windows.h>
#include <list>
#include <vector>

LARGE_INTEGER t1,t2,tc; //用於計時

int main()
{
	const int N = 100000;
	int* a = new int[N];
	std::list<int>* plist = new std::list<int>(a, a + N);
	std::vector<int>* pVec = new std::vector<int>(a, a + N);

	QueryPerformanceFrequency(&tc);
	QueryPerformanceCounter(&t1);
	delete plist;
	QueryPerformanceCounter(&t2);
	printf("List Use Time:%f\n",(t2.QuadPart - t1.QuadPart)*1.0/tc.QuadPart);

	QueryPerformanceCounter(&t1);
	delete pVec;
	QueryPerformanceCounter(&t2);
	printf("Vector Use Time:%f\n",(t2.QuadPart - t1.QuadPart)*1.0/tc.QuadPart);

	return 0;
}

這裡的list和vector我使用了動態分配,主要是為了方便計算其析構時的用時,在棧上分配也類似

在我電腦上得到的結果如下:

這足矣看出list在析構時(特別是大量資料時)效率極低

當然對於大量資料的情況,可以使用boost中的fast_pool_allocator來作為空間配置器,其比較適合於一次請求單個大記憶體塊的情況

下面是上面程式改用fast_pool_allocator作為空間配置器後的測試,我使用的boost版本為boost_1_55_0

#include <Windows.h>
#include <list>
#include <vector>
#include <boost\pool\pool_alloc.hpp>

LARGE_INTEGER t1,t2,tc; //用於計時

int main()
{
	const int N = 100000;
	int* a = new int[N];
	std::list<int, boost::fast_pool_allocator<int> >* plist = new std::list<int, boost::fast_pool_allocator<int> >(a, a + N);
	std::vector<int, boost::fast_pool_allocator<int> >* pVec = new std::vector<int, boost::fast_pool_allocator<int> >(a, a + N);

	QueryPerformanceFrequency(&tc);
	QueryPerformanceCounter(&t1);
	delete plist;
	QueryPerformanceCounter(&t2);
	printf("List Use Time:%f\n",(t2.QuadPart - t1.QuadPart)*1.0/tc.QuadPart);

	QueryPerformanceCounter(&t1);
	delete pVec;
	QueryPerformanceCounter(&t2);
	printf("Vector Use Time:%f\n",(t2.QuadPart - t1.QuadPart)*1.0/tc.QuadPart);

	return 0;
}

測試結果如下:

從測試結果可以發現,對於大資料情況下,使用fast_pool_allocator作為空間配置器後list可以提高約50%以上的效率

但即使提高了50%的效率,其效率仍然很低

同時,fast_pool_allocator也有一些自身的缺陷,如其自身有一部分記憶體是不會被釋放的, 這將導致記憶體洩露

筆者之前一程式就需要大量使用這種的大量資料的list,於是我開始思考怎樣進行效率的提升

通過不斷嘗試,得到了一個相對較好的方法:

通過包裝STL中的list,寫出了QuickList類

QuickList支援所有list支援的操作,其內部有一個作為底層容器的list指標,採用動態分配形式

當需要clear()或析構物件時,將採用推遲釋放記憶體的機制,也就是將list在堆上分配的記憶體地址copy到靜態變數DelListPtr中,不進行釋放的操作

至於DelListPtr中的殘留記憶體資料,可以由程式設計師選擇時機自行釋放,如選擇在程式空閒時釋放或結束時再釋放等等

QuickList程式碼如下:

#include <list>
#include <vector>

template<class T, class Alloc = std::allocator<T> >
class QuickList
{
public:
    typedef   typename std::list<T, Alloc>::value_type                value_type;
    typedef   typename std::list<T, Alloc>::pointer                   pointer;
    typedef   typename std::list<T, Alloc>::const_pointer             const_pointer;
    typedef   typename std::list<T, Alloc>::reference                 reference;
    typedef   typename std::list<T, Alloc>::const_reference           const_reference;
    typedef   typename std::list<T, Alloc>::size_type                 size_type;
    typedef   typename std::list<T, Alloc>::difference_type           difference_type;
    typedef   typename std::list<T, Alloc>::iterator                  iterator;
    typedef   typename std::list<T, Alloc>::const_iterator            const_iterator;
    typedef   typename std::list<T, Alloc>::reverse_iterator          reverse_iterator;
    typedef   typename std::list<T, Alloc>::const_reverse_iterator    const_reverse_iterator;
    typedef   typename std::list<T, Alloc>::allocator_type            allocator_type;

public:
    QuickList() : pList(new std::list<T, Alloc>()){}
    explicit QuickList(size_type n, const T& elem = T()) : pList(new std::list<T, Alloc>(n, elem)){}
    template<class InputIterator> QuickList(InputIterator f, InputIterator l) : pList(new std::list<T, Alloc>(f, l, A)){}
    QuickList(const std::list<T, Alloc>& list) : pList(new std::list<T, Alloc>(list)){}
    QuickList(const QuickList<T, Alloc>& qlist) : pList(new std::list<T, Alloc>(*qlist.pList)){}
    QuickList(QuickList<T, Alloc>&& qlist)
    {
        if(qlist.pList)
        {
            pList = qlist.pList;
            qlist.pList = nullptr;
        }
        else
            pList = new std::list<T, Alloc>();
    }
    QuickList<T, Alloc>& operator=(const QuickList<T, Alloc>& qlist)
    {
        if(this != &qlist)
        {
            DelayRelease();
            pList = new std::list<T, Alloc>(*qlist.pList);
        }
        return *this;
    }
    QuickList<T, Alloc>& operator=(QuickList<T, Alloc>&& qlist)
    {
        if(this != &qlist)
        {
            DelayRelease();
            if(qlist.pList)
            {
                pList = qlist.pList;
                qlist.pList = nullptr;
            }
            else
                pList = new std::list<T, Alloc>();
        }
        return *this;
    }
    ~QuickList()
    {
        DelayRelease();
    }

protected:
    void DelayRelease()
    {
        if(pList)
            DelListPtr.push_back(pList);
    }

public: //重新初始(不建議使用clear)
    void initialize()
    {
        DelayRelease();
        pList = new std::list<T, Alloc>();
    }
    void initialize(size_type n, const T& elem = T())
    {
        DelayRelease();
        pList = new std::list<T, Alloc>(n, elem);
    }
    template<class InputIterator> void initialize(InputIterator f, InputIterator l)
    {
        DelayRelease();
        pList = new std::list<T, Alloc>(f, l);
    }
    template<class _Alloc> void initialize(const std::list<T, _Alloc>& list)
    {
        DelayRelease();
        pList = new std::list<T, Alloc>(list);
    }
    template<class _Alloc> void initialize(const QuickList<T, _Alloc>& list)
    {
        DelayRelease();
        pList = new std::list<T, Alloc>(*qlist.pList);
    }

public: //介面函式
    iterator begin()
    {
        return pList->begin();
    }
    iterator end()
    {
        return pList->end();
    }
    const_iterator begin() const
    {
        return pList->begin();
    }
    const_iterator end() const
    {
        return pList->end();
    }
    reverse_iterator rbegin()
    {
        return pList->rbegin();
    }
    reverse_iterator rend()
    {
        return pList->rend();
    }
    const_reverse_iterator rbegin() const
    {
        return pList->rend();
    }
    const_reverse_iterator rend() const
    {
        return pList->rbegin();
    }
    size_type size() const
    {
        return pList->size();
    }
    size_type max_size() const
    {
        return pList->max_size();
    }
    bool empty() const
    {
        return pList->empty();
    }
    allocator_type get_allocator() const
    {
        return pList->get_allocator();
    }
    void swap(std::list<T, Alloc>& list)
    {
        return pList->swap(list);
    }
    void swap(QuickList<T, Alloc>& qlist)
    {
        std::swap(pList, qlist.pList);
    }
    reference front()
    {
        return pList->front();
    }
    const_reference front() const
    {
        return pList->front();
    }
    reference back()
    {
        return pList->back();
    }
    const_reference back() const
    {
        return pList->back();
    }
    void push_front(const T& elem)
    {
        return pList->push_front(elem);
    }
    void push_back(const T& elem)
    {
        return pList->push_back(elem);
    }
    void pop_front()
    {
        return pList->pop_front();
    }
    void pop_back()
    {
        return pList->pop_back();
    }
    iterator insert(iterator pos, const T& elem)
    {
        return pList->insert(pos, elem);
    }
    template<class InputIterator> void insert(iterator pos, InputIterator f, InputIterator l)
    {
        return pList->insert(pos, f, l);
    }
    iterator insert(iterator pos, size_type n, const T& elem)
    {
        return pList->insert(pos, n, elem);
    }
    iterator erase(iterator pos)
    {
        return pList->erase(pos);
    }
    iterator erase(iterator f, iterator l)
    {
        return pList->erase(f,l);
    }
    void clear()
    {
        return pList->clear();
    }
    void resize(size_type n, const T& elem = T())
    {
        return pList->resize(n, elem);
    }
    template<class InputIterator> void assign(InputIterator f, InputIterator l)
    {
        return pList->assign(f, l);
    }
    void assign(size_type n, const T& elem)
    {
        return pList->assign(n, elem);
    }
    void splice(iterator pos, std::list<T, Alloc>& list)
    {
        return pList->splice(pos, list);
    }
    void splice(iterator pos, QuickList<T, Alloc>& qlist)
    {
        return pList->splice(pos, *qlist.pList);
    }
    void splice(iterator pos, std::list<T, Alloc>& list, iterator i)
    {
        return pList->splice(pos, list, i);
    }
    void splice(iterator pos, QuickList<T, Alloc>& qlist, iterator i)
    {
        return pList->splice(pos, *qlist.pList, i);
    }
    void splice(iterator pos, std::list<T, Alloc>& list, iterator f, iterator l)
    {
        return pList->splice(pos, list, f, l);
    }
    void splice(iterator pos, QuickList<T, Alloc>& qlist, iterator f, iterator l)
    {
        return pList->splice(pos, *qlist.pList, f, l);
    }
    void remove(const T& val)
    {
        return pList->remove(val);
    }
    template <class Predicate> void remove_if(Predicate p)
    {
        return pList->remove_if(p);
    }
    void unique()
    {
        return pList->unique();
    }
    template <class BinaryPredicate> void unique(BinaryPredicate p)
    {
        return pList->unique(p);
    }
    void merge(std::list<T, Alloc>& list)
    {
        return pList->merge(list);
    }
    void merge(QuickList<T, Alloc>& qlist)
    {
        return pList->merge(*qlist.pList);
    }
    template<class StrictWeakOrdering> void merge(std::list<T, Alloc>& list, StrictWeakOrdering comp)
    {
        return pList->merge(list, comp);
    }
    template<class StrictWeakOrdering> void merge(QuickList<T, Alloc>& qlist, StrictWeakOrdering comp)
    {
        return pList->merge(*qlist.pList, comp);
    }
    void reverse()
    {
        return pList->reverse();
    }
    void sort()
    {
        return pList->sort();
    }
    template<class StrictWeakOrdering> void sort(StrictWeakOrdering comp)
    {
        return pList->sort(comp);
    }
    bool operator==(const std::list<T, Alloc>& list)
    {
        return *pList == list;
    }
    bool operator==(const QuickList<T, Alloc>& qlist)
    {
        return *qlist.pList == list;
    }
    bool operator<(const std::list<T, Alloc>& list)
    {
        return *pList < list;
    }
    bool operator<(const QuickList<T, Alloc>& qlist)
    {
        return *qlist.pList < list;
    }

public:
    const std::list<T, Alloc>& get_list() const
    {
        return *pList;
    }
    std::list<T, Alloc>& get_list()
    {
        return *pList;
    }

protected:
    std::list<T, Alloc>* pList;

public:
    static std::vector<std::list<T, Alloc>*> DelListPtr; //注:由於在單執行緒中訪問,固沒有對DelListPtr進行加鎖操作
};


//初始靜態成員變數
template<class T, class Alloc> std::vector<std::list<T>*> QuickList<T,Alloc>::DelListPtr = std::vector<std::list<T>*>();




QuickList使用時需要注意以下幾點:

1、如果釋放資源的執行緒是另一個執行緒,需要進行加鎖,我程式碼是用於同一執行緒中,在MFC空閒迴圈中清理,所以沒寫這部分程式碼

2、從需要釋放記憶體到完全釋放記憶體將會存在一個延遲,對於記憶體緊張的情況可能導致記憶體不能很好的週轉

3、對於需要使用list的函式,可以通過get_list函式取得QuickList中的底層list容器,但需要程式設計師確保進行安全的操作

4、QuickList中大部分函式都是作為介面,實際呼叫的是list中的函式,clear函式同樣也是,不過該類實現了一個initialize函式,建議用其代替clear

5、釋放DelListPtr中的殘留記憶體是程式設計師的責任,通常可以選擇在程式空閒時間或結束時釋放,如不希望釋放時對使用者產生影響,可以考慮程式空閒時分片釋放(如每次刪除list中1000個元素)

當然,你也可以通過自己開發空間配置器等方式來提升效率,歡迎大家一起研究,歡迎大家批評交流