关闭

C++标准模板库中list大数据量情况下析构效率的研究

标签: C++list效率析构QuickList
1468人阅读 评论(0) 收藏 举报
分类:

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个元素)



当然,你也可以通过自己开发空间配置器等方式来提升效率,欢迎大家一起研究,欢迎大家批评交流

1
0

查看评论
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
    个人资料
    • 访问:15792次
    • 积分:276
    • 等级:
    • 排名:千里之外
    • 原创:5篇
    • 转载:3篇
    • 译文:0篇
    • 评论:22条
    文章分类
    最新评论