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也有一些自身的缺陷,如其自身有一部分内存是不会被释放的, 这将导致内存泄露
具体可以参看: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个元素)
当然,你也可以通过自己开发空间配置器等方式来提升效率,欢迎大家一起研究,欢迎大家批评交流