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個元素)
當然,你也可以通過自己開發空間配置器等方式來提升效率,歡迎大家一起研究,歡迎大家批評交流