许式伟 ID:xushiweizh
419013次访问,排名112好友3人,关注者38
xushiweizh的文章
原创 125 篇
翻译 0 篇
转载 11 篇
评论 927 篇
许式伟的公告

本博客内容除非特殊说明均属原创,如需转载、引用其中的部分文字,请注意以下几点:

1)请在转载(引用)的内容开始添加本人署名,并提供本博客中相应文章的链接。如你的作品为非电子读物或纯文本,请给出链接的url。

2)请勿用于商业用途。

3)如果愿意,请给我邮件:xushiweizh@gmail.com,让我知道我的东西到哪去了。谢过。

重要链接


订阅

最近评论
yefeng_ok:非常期待C++0x标准的出台。
yefeng_ok:非常期待C++0x标准的出台。
LiYanRui:能不能把 free software 翻译为“自由软件”呢?

cairo 项目主页上很明确地说了:you should think of ``free'' as in ``free speech,'' not as in ``free beer.''
zhangyaoting196:

WWW.soAsp.net 编程学习网 技术+ 实例应用 讲解不错。 推荐大家!

有很多 技术资料也很好!




ttkk1024:给老大顶一下
文章分类
收藏
相册
DocX预览图
Google vs. 百度
WinX相关
WINX团队
ebasil的专栏(RSS)
VisualFC/WINX专栏(RSS)
任风行(一路奔跑)(RSS)
绅士亦花心之WINX相关(RSS)
许伟群的专栏(RSS)
友情链接
QWL1996的专栏(RSS)
Sting的专栏(RSS)
SunHui的专栏(RSS)
不亦快斋(RSS)
于无声处(RSS)
手机开发论坛
珠穆朗玛(老汉)(RSS)
福&柯实验室(RSS)
存档
软件项目交易
订阅我的博客
XML聚合  FeedSky
订阅到鲜果
订阅到Google
订阅到抓虾
订阅到BlogLines
订阅到Yahoo
订阅到GouGou
订阅到飞鸽
订阅到Rojo
订阅到newsgator
订阅到netvibes

原创 C++内存管理变革(7):基于ScopeAlloc的STL容器收藏

新一篇:  自动化单元测试的意义 | 旧一篇: C++内存管理变革(6):通用型垃圾回收器 - ScopeAlloc

C++内存管理变革(7):基于ScopeAlloc的STL容器

许式伟
2008-2-4

来由

在前文(请参阅《C++内存管理变革(6):通用型垃圾回收器 - ScopeAlloc》),我们介绍了ScopeAlloc。既然我们称之为一个通用型的GC Allocator,那么这里我们就谈谈如何用ScopeAlloc来改造STL的容器,它们包括:std::list, std::map, std::set, std::multimap, std::multiset。

改造方法

不改变std::list, std::map, std::set, std::multimap, std::multiset等STL容器的用法,仅仅将其Allocator换成ScopeAlloc。做法不难想到,这需要我们提供一个GC Allocator到STL的Allocator的Wrapper类。它就是我们的StlAlloc类。

StlAlloc

代码参见:

template <class _Ty, class _Alloc = ScopeAlloc>
class StlAlloc
{
private:
_Alloc* m_alloc;

public:
typedef size_t size_type;
typedef ptrdiff_t difference_type;
typedef _Ty* pointer;
typedef const _Ty* const_pointer;
typedef _Ty& reference;
typedef const _Ty& const_reference;
typedef _Ty value_type;

public:
pointer address(reference val) const
{ return &val; }
const_pointer address(const_reference val) const
{ return &val; }

size_type max_size() const
{ size_type count = (size_type)(-1) / sizeof (_Ty);
return (0 < count ? count : 1); }

public:
StlAlloc(_Alloc& alloc) : m_alloc(&alloc) {}

pointer allocate(size_type count, const void*)
{ return m_alloc->allocate(count * sizeof(_Ty)); }
void deallocate(void* p, size_type cb)
{ m_alloc->deallocate(p, cb); }
void construct(pointer p, const _Ty& val)
{ new(p) _Ty(val); }
void destroy(pointer p)
{ p->~_Ty(); }

public:
char* _Charalloc(size_type cb)
{ return (char*)m_alloc->allocate(cb); }
};
  • 关于StlAlloc,并没有特别重要的细节需要交代。只有一个细节,是非常有意思的,就是Visual C++ 6.0为allocator引入了一个特殊的函数:_Charalloc。这个函数的功能类似于malloc,用于分配一块不确定企图的普通内存块(请注意,实际上allocator的本意要避免使用这样的方法,allocator分配的内存应该是签名的,或者说带类型信息的)。

再谈STL的Allocator

实际上,STL的Allocator推荐使用rebind技巧解决“同一个STL容器需要分配多种类型的对象”问题。关于allocator详细规格和rebind技术的细节,请参阅《CUJ:标准库:Allocator能做什么?》。Visual C++ 6.0版本带的STL引入了特殊的_Charalloc函数,其目的是规避标准中要求STL allocator实现的rebind而已。

不同版本的STL,其allocator规格存在不一致性。例如:

  • Visual C++ 6.0之STL库引入了特殊的_Charalloc。
  • SGI STL有自己的alloc组件,和我们这里的StlAlloc一样,SGI STL的std::allocator只是自己的alloc组件的一个Wrapper。并且默认情形下使用alloc组件,而不是std:: allocator。这说明SGI STL的设计者并不喜欢标准的allocator。

这种不一致性的本质原因是,STL的allocator存在一定的设计缺陷。为了更加“C++”,设计者试图去让allocator分配的内存是签 名的(或者说带类型信息的)。但是,签名意味着它不是通用的内存管理组件,无法取代new/delete, malloc/free的地位。为了避免暴露类的实现细节,_Charalloc这样的东西的存在就在所难免了(rebind技术是繁琐且丑陋的)。

改造后的STL容器

改造后的STL容器,我们命名为std::List, std::Map, std::Set, std::MultiMap, std::MultiSet。其源码请参考:

实际上我们除了重写了构造函数,并没有改变任何东西。以Map类为例,改造的代码如下:

template <
class KeyT, class DataT,
class PredT = std::less<KeyT>,
class AllocT = ScopeAlloc
>
class Map : public std::map< KeyT, DataT, PredT, StlAlloc<DataT, AllocT> >
{
private:
typedef StlAlloc<DataT, AllocT> _Alloc;
typedef std::map<KeyT, DataT, PredT, _Alloc> _Base;

public:
explicit Map(AllocT& alloc, const PredT& pred = PredT())
:
_Base(pred, alloc)
{
}

template <class Iterator>
Map(AllocT& alloc, Iterator first, Iterator last, const PredT& pred = PredT())
:
_Base(first, last, pred, alloc)
{
}
};

使用改造后的STL容器

一个简单的样例如下:

typedef std::Map<int, int> MapT;

std::BlockPool recycle;
std::ScopeAlloc alloc(recycle);

MapT coll(alloc);
coll.insert(MapT::value_type(1, 2));
coll.insert(MapT::value_type(1, 4));
coll.insert(MapT::value_type(2, 4));
coll.insert(MapT::value_type(4, 8));

实际上,也可以使用AutoFreeAlloc作为Allocator,如下:

typedef std::Map<int, int, std::less<int>, std::AutoFreeAlloc> MapT;

std::AutoFreeAlloc alloc;
MapT coll(alloc);

coll.insert(MapT::value_type(1, 2));
coll.insert(MapT::value_type(1, 4));
coll.insert(MapT::value_type(2, 4));
coll.insert(MapT::value_type(4, 8));

特别提醒:

请注意:虽然支持AutoFreeAlloc,但是由于AutoFreeAlloc本身的局限性,使得这样的STL容器仅限于有限的场合。请注意其应用场景。

改造后的STL容器的性能评估

虽然结果是不言而喻的,但也许你仍然关心关于改造后的STL容器性能的具体数据。这一小结就进行简单对比测试。如下:

结论是:

使用ScopeAlloc或者AutoFreeAlloc后,STL容器的性能有显著提高。而ScopeAlloc与AutoFreeAlloc在该容器存在大量内存分配时区别不显著。另外,由于内存池技术的作用,ScopeAlloc在Share同一个BlockPool后,性能略有改善。

为什么没有std::vector等容器?

也许你很奇怪,既然ScopeAlloc可以改善STL容器的性能,为什么我们不将它应用到所有的STL容器。其实,问题在于其实那些容器它并不需要ScopeAlloc。这些容器包括:

  • std::vector
  • std::basic_string, std::string, std::wstring
  • std::deque, std::stack, std::queue

无论是AutoFreeAlloc,还是ScopeAlloc,以及其他所有号称可以改善内存分配性能的Allocator, 仅仅适用于小内存管理。对于那些本身是线性内存结构的(如:std::vector, std::basic_string, std::string, std::wstring等),或者已经采用MemBlock(内存块)结构的(如:std::deque, std::stack, std::queue等),并不需要他们。这些容器并不需要一个特别的Allocator,普通的new/delete足矣。

发表于 @ 2008年02月04日 15:03:00|评论(loading...)|编辑

新一篇:  自动化单元测试的意义 | 旧一篇: C++内存管理变革(6):通用型垃圾回收器 - ScopeAlloc

评论

#Edison1024 发表于2008-02-18 20:31:23  IP: 219.159.199.*
替换掉STL的分配器换成scopeallo以后,性能毫无疑问会提升。但是STL本身是线程安全的,这么一来只能在非多线程环境下使用了吧?
#Edison1024 发表于2008-02-18 20:31:50  IP: 219.159.199.*
替换掉STL的分配器换成scopeallo以后,性能毫无疑问会提升。但是STL本身是线程安全的,这么一来只能在非多线程环境下使用了吧?
#xushiweizh 发表于2008-02-19 08:23:03  IP: 219.131.196.*
1. STL容器都是非线程安全的,你需要自己进行互斥。
2. Java的容器倒确实是线程安全的。不过,我并不认为Java的设计比STL的好。为了线程安全这四个字,Java牺牲了多少,而实际上仍然无法全部解决线程安全问题。
3. 对于多线程安全问题,我的观念很简单,就是任何可直接使用的类(指那些被库用户直接调用的类,如ScopeAlloc)都不要考虑“线程”安全问题。其实STL也是本着这样的理念设计的。详细参考: http://cpp.winxgui.com/cn:parallel-programming-innovation。我后续还会涉及相关话题。
#christanxw 发表于2008-07-26 00:13:51  IP: 124.40.132.*
sgi stl的allocator是线程安全的。它提供了3种分配器:
1. alloc,默认的分配器,线程安全的,通常也是性能最佳的。
2.pthread_alloc,也是线程安全的,是每个线程一个分配器,因此不用任何锁机制。速度自然比alloc更快快,但在这个线程deallocate的内存不能在另一个线程使用。
3.single_client_alloc,不是线程安全的,因此速度也可能比alloc快,适用于单线程的程序。
#xushiweizh 发表于2008-07-26 11:53:17  IP: 221.219.221.*
to christanxw:
使得,stl的allocator是线程安全的(不只是sgi stl)这样,上面我说的是:stl的容器是非线程安全的。不过我倒还没有测试过sgi的single_client_alloc的性能,回头测试下看。
发表评论  


当前用户设置只有注册用户才能发表评论。如果你没有登录,请点击登录
Csdn Blog version 3.1a
Copyright © 许式伟