STL——空间配置器(SGI-STL)
一、 空间配置器标准接口
参见《STL源码剖析》第二章-2.1。<memory>文件。
二、具备次配置力的SGI空间配置器
1. SGI STL的配置器与众不同,也与标准规范不同,其名称是alloc而非allocator,而且不接受任何参数(虽然SGI也定义有一个符合部分标准、名为sllocator的配置器,但SGI自己从未用过它,也不建议使用,主要因为效率不佳,它只是基层内存配置/释放行为(也就是::operator new和 ::operator delete)的一层薄薄的包装,并没有考虑到任何效率上的强化)。这并不会带来什么困扰:我们通常很少需要自行指定配置器名称,而SGI STL的每一个容器都已经指定其缺省的空间配置器为alloc。
// 在程序中要明白采用SGI配置器,则不能采用标准写法:
vector <int, std::allocator<int> > iv; // 标准写法,in VC or CB
vector <int, std::alloc> iv; // SGI,in GCC // SGI STL 每一个容器都已经指定缺省空间配置器
template <class T, class Alloc = alloc > // 缺省使用alloc为配置器
class vector { ... };
2. SGI特殊的空间配置器——std::alloc
一般而言,我们所习惯的C++内存配置操作和释放操作是这样的:
class Foo { ... };
Foo* pf = new Foo; // 配置内存,然后构造对象
delete pf; // 将对象析构,然后释放内存
这其中的new算式内含两阶段操作:(1)调用::operator new 配置内存,(2)调用Foo::Foo() 构造对象内容。delete算式也内含两阶段操作:(1)调用Foo::~Foo() 将对象析构;(2)调用 ::operator delete 释放内存。
为了精密分工,STL allocator 决定将这两个阶段操作区分开来。内存配置操作由alloc::allocate()负责,内存释放操作由 alloc::deallocate() 负责;对象构造操作由 ::construct()负责,对象析构操作由::destroy()负责。STL配置器定义于<memory>之中,实现在于内含的<stl_alloc.h> 和 <stl_construct.h> 两个文件之中。
3. 构造和析构基本工具:construct() 和 destroy()
上述construct()接受一个指针p和一个初值value,该函数的用途就是将初值设定到指针所指的空间上。C++的placement new 运算子可用来完成这一任务。
destroy()有两个版本,第一版本接受一个指针,准备将该指针所指之物析构掉。这很简单,直接调用该对象的析构函数即可。第二版本接受first和last两个迭代器,准备将[ first, last )范围内的所有对象析构掉(注意,这是一个左闭右开的范围)。如果范围很大,而每个对象的析构函数都无关痛痒(所谓trivial destructor),那么一次次调用这些无关痛痒的析构函数,对效率是一种伤害。因此,这里首先利用value_type()获得迭代器所指对象的型别,再利用__type_traits<T>判断该型别的析构函数是否无关痛痒。若是(__true_type),则什么也不做就结束;若否(__false_type),这才以循环方式巡防整个范围,并在循环中每经历一个对象就调用第一个版本的destroy()。 (上述value_type()和__type_traits<>在《STL源码剖析》3.7节有详细介绍。)
4. 空间的配置和释放,std::alloc
对象构造前的空间配置和对象析构后的空间释放,由<stl_alloc.h>负责,SGI对此的设计哲学如下:
(1)向system heap 要求空间;
(2)考虑多线程(multi-threads)状态;
(3)考虑内存不足时的应变措施;
(4)考虑过多“小型区块”可能造成的内存碎片(fragment)问题。
C++的内存配置基本操作是::operator new(),内存释放基本操作是::operator delete()。这两个全局函数相当于C的malloc() 和 free() 函数。SGI正是以malloc()和free() 完成内存的配置和释放。考虑到小型区块所可能造成的内存破碎问题,SGI 设计了双层级配置器,第一级配置器直接使用malloc() 和 free() ,第二级配置器则是情况采用不同的策略:以配置128bytes区块为界,大于则调用第一级配置器,小于则采用复杂的memory pool整理方式,同时也取决是否定义了_USE_MALLOC。
#ifdef _USE_MALLOC
...
typedef __malloc_alloc_template<> malloc_alloc;
typedef malloc_alloc alloc; // 令alloc为第一级配置器
#else
...
// 令alloc为第二级配置器
typedef __default_alloc_template<__NODE_ALLOCATOR_THREADS, > alloc;
#endif /* ! _USE_MALLOC*/
其中__malloc_alloc_template就是第一级配置器,__default_alloc_template就是第二级配置器。注意:alloc并不接受任何template参数。
无论alloc被定义为第一级配置器或第二级配置器(SGI STL容器缺省使用第二级配置器),SGI还为它再包装一个接口如下,使配置器的接口能够符合STL规格:
// 其内部的四个成员函数其实都是单纯的转调用,调用传递给配置器的成员函数
template<class T, class Alloc>
class simple_alloc {
public:
static T *allocate(size_t n)
{ return == n ? : (T*)Alloc::allocate(n * sizeof(T)); }
static T *allocate(void)
{ return (T*)Alloc::allocate(n * sizeof(T)); }
static T *deallocate(T *p, size_t n)
{ if( != n) ? : (T*)Alloc::deallocate(p, n * sizeof(T)); }
static T *allocate(size_t n)
{ Alloc::deallocate(p, sizeof(T)); } };
SGI STL容器全都使用这个simple_alloc接口:
template <class T, class Alloc = alloc> // 缺省使用alloc为配置器
class vector{
protected:
// 专属之空间配置器,每次配置一个元素大小
typedef simple_alloc<value_type, Alloc> data_allocator;
void deallocte() {
if ( ... )
data_allocator::deallocate(start, end_of_storage - start);
}
...
};
5. 第一级配置器 __malloc_alloc_template 剖析 第一级配置器以malloc(), free(), realloc() 等C函数执行实际的内存配置、释放、重配置操作,并实现出类型C++ new-handler的机制。是的,它不能直接运用C++ new-handler机制,因为它并非使用::operator new来配置内存。注意,它没有“template型别参数”。参见相关源码。
6. 第二级配置器(缺省) __default_alloc_template剖析 第二级配置器多了一些机制,避免太多小额区块造成内存的碎片和配置时的额外负担。SGI第二级配置器的做法是:如果区块够大,超过128bytes时,就移交第一级配置器处理。当区块小于128bytes时,则以内存池(memory pool)管理,此法又称为次层配置(sub-allocation):每次配置一大块内存,并维护对应之自由链表(free-list)。下次若再有相同大小的内存需求,就直接从free-list中拨出。如果客户端释还小额区块,就由配置器回收到free-list中——是的,别忘了,配置器除了负责配置,也负责回收。为了管理方便,SGI第二级配置器会主动将任何小额区块的内存需求量上调至8的倍数(例如客户端要求30bytes,就自动调整为32bytes),并维护16个free-list(128Bytes/8倍 = 16个free_list),各自管理大小分别为8, 16, 24, 32, 40, 48, 56, 64, 72, 80, 88, 96, 104, 112, 120, 128bytes的小额区块。free-list的节点结构如下:
// 使用union类型,不会为了维护链表所必须的指针而造成内存的另一种浪费
union obj
{
union obj * free_list_link;
char client_data[]; // the client sees this
};
参见相关源码。
7. 空间配置函数allocate()
8. 空间释放函数 deallocate()
9. 重新填充free lists 当发现free-list中没有可用区块了时,就调用refill(),准备为free list重新填充空间。新的空间将取自内存池(经由chunk_alloc完成)。缺省取得20个新节点(新区块),但万一内存池空间不足,获得的节点数(区块数)可能小于20。参见相关源码。
10. 内存池(memory pool) 从内存池中取空间给free list 使用,是chunk_alloc() 的工作。参见相关源码。 chunk_alloc()函数以end_free - start_free来判断内存池的水量。如果水量充足,就直接调出20个区块返回给free list。如果水量不足以提供20个区块,但还足够供应一个以上的区块,就拨出这不足20个区块的空间出去。这时候其pass by reference 的nobjs 参数将被修改为实际能够供应的区块数。如果内存池连一个区块空间都无法供应,对客户端显然无法交待,此时便需利用malloc()从heap中配置内存,为内存池注入源头活水以应付需求。新水量的大小为需求量的两倍,再加上一个随着配置次数增加而愈来愈大的附加量。
万一,整个system heap空间都不够了(以至于无法为内存池注入源头活水),malloc() 行动失败,chunk_alloc() 就四处寻找有无“尚有未用区块,且区块够大(也即,不再仅仅只是找大小为size的free_list区块,只要区块大于size,就满足要求,也会被交出。)”之free list。找到了就挖一块交出,找不到就调用第一级配置器。第一级配置器其实也是使用malloc()来配置内存,但它有out-of-memory处理机制(类似new-handler机制),或许有机会释放其他的内存拿来此处用。如果可以,就成功,否则发出bad-alloc异常。
三、内存基本处理工具
STL 定义有五个全局函数,作用于未初始化空间上。这样的功能对于容器的实现很有帮助,在《STL源码剖析》第4章容器实现代码中,看到它们肩负的重任。前两个函数是前面说过的、用于构造的construct() 和用于析构的 destory() ,另三个函数是 uninitialized_copy(), uninitialized_fill(), uninitializd_fill_n(),分别对应于高层次函数copy(), fill(), fill_n()——这些都是STL算法,在第6章介绍。如果要使用本节的三个低层次函数,应该包含<memory>,不过SGI 把它们实际定义于<stl_uninitialized>。
1. uninitialized_copy
template <class InputIterator, class ForwardIterator>
ForwardIterator
uninitialized_copy( InpuIterator first, InpuIterator last,
ForwardIterator result);
uninitialized_copy() 使我们能够将内存的配置与对象的构造行为分离开来。如果作为输出目的地的[ result, result + (last - first))范围内的每一个迭代器都指向未初始化区域,则uninitialized_copy() 会使用copy constructor,给身为输入来源之[first, last) 范围内的每一个对象产生一份复制品,放进输出范围中。(使用上面的construct 构造工具)。也就是说,针对输入范围内的每一个迭代器i,该函数会调用 construct( &*(result+(i-first)), *i ), 产生 *i 的复制品,放置于输出范围的相对位置上。
这是一个非常有用的工具,因为容器的全区间构造函数通常以两个步骤完成:
(1)配置内存区块,足以包含范围内的所有元素。
(2)使用uninitialized_copy() ,在该内存区块上构造元素。
2. uninitialized_fill
template <class FrowardIterator, class T>
void uninitialized_fill(FrowardIterator first, FrowardIterator last, const T& x);
uninitialized_fill() 也能够使我们将内存配置与对象的构造行为分离开来。如果[ first, last ) 范围内的每个迭代器都指向未初始化的内存,那么uninitialized_fill() 会在该范围内产生x(上式第三参数)的复制品。
注意:与uninitialized_copy() 一样,uninitialized_fill() 必须具备 “commit or rollback”语意,换句话说,它要么产生出所有必要元素,要么不产生任何元素(异常安全等级)。如果有任何一个copy constructor 丢出异常,uninitialized_fill 必须能够将已产生的所有元素析构掉。
3. uninitialized_fill_n
template <class ForwardIterator, class Size, class T>
ForwardIterator
uninitialized_fill_n(ForwardIterator first, Size n, const T& x);
uninitialized_fill_n() 能够使我们将内存配置与对象构造行为分离开来。它会为指定范围内的所有元素设定相同的初值。
如果[ first, first+n )范围内的每一个迭代器都指向未初始化的内存,那么 uninitialized_fill_n() 会调用copy constructor ,在该范围内产生x(上式第三参数)的复制品。uninitialized_fill_n() 也具有 “commit or rollback”语意。
这三个函数的实现法参见相关源码。其中所呈现的 iterators(迭代器)、value_type()、 __type_traits、__true_type、__false_type、is_POD_type等实现技术,在《STL源码剖析》都有详细介绍。
STL——空间配置器(SGI-STL)的更多相关文章
- STL——空间配置器(构造和析构基本工具)
以STL的运用角度而言,空间配置器是最不需要介绍的东西,它总是隐藏在一切组件(更具体地说是指容器,container)的背后,默默工作,默默付出.但若以STL的实现角度而言,第一个需要介绍的就是空间配 ...
- stl空间配置器简介
1. 符合STL标准的空间配器接口 STL是c++中使用非常广泛的一个标准库,它包含各种有用的容器.而空间配置器作为STL各种容器的背后的核心,负责容器内部内存的分配和释放.不过空间配置器可以分配的也 ...
- 【陪你系列】5 千字长文+ 30 张图解 | 陪你手撕 STL 空间配置器源码
大家好,我是小贺. 点赞再看,养成习惯 文章每周持续更新,可以微信搜索「herongwei」第一时间阅读和催更,本文 GitHub https://github.com/rongweihe/MoreT ...
- 【转】STL空间配置器
STL空间配置器(allocator)在所有容器内部默默工作,负责空间的配置和回收.STL标准为空间配置器定义了标准接口(可见<STL源码剖析>P43).而具体实现细节则由各编译器实现版本 ...
- STL空间配置器
1.什么是空间配置器? 空间配置器负责空间配置与管理.配置器是一个实现了动态空间配置.空间管理.空间释放的class template.以内存池方式实现小块内存管理分配.关于内存池概念可以点击:内存池 ...
- 咬碎STL空间配置器
STL空间配置器 一.开场白: 给我的感觉就是,了解是空间配置器的功能,是那么的明了:在看原理,我还是很开心:接下来是360度大转变: 那么长的变量或者函数命名.那么多的宏.不爽,不过,遇上我这种二货 ...
- stl空间配置器线程安全问题补充
摘要 在上一篇博客<STL空间配置器那点事>简单介绍了空间配置器的基本实现 两级空间配置器处理,一级相关细节问题,同时简单描述了STL各组件之间的关系以及设计到的设计模式等. 在最后,又关 ...
- STL空间配置器那点事
STL简介 STL(Standard Template Library,标准模板库),从根本上说,STL是一些“容器”的集合,这些“容器”有list,vector,set,map等,STL也是算法和其 ...
- STL空间配置器解析和实现
STL空间配置器的强大和借鉴作用不言而喻,查阅资料,发现了Dawn_sf已经对其有了极其深入和详细的描述,所以决定偷下懒借用其内容,只提供自己实现STL空间配置器的源码,具体解析内容参考:(一)STL ...
随机推荐
- java SoftReference WeakReference
Java 2 平台引入了 java.lang.ref 包,其中包括的类可以让您引用对象,而不将它们留在内存中.这些类还提供了与垃圾收集器(garbage collector)之间有限的交互. 1.先“ ...
- 关于Unity中Cg的基本语法和使用
Cg是类似于C语言的发展起来的图形编程语言,Cgraphics,它的很多表达式if...else...和C语言非常相像,也和C#非常相像. 由于Shader是写给显卡执行的,所以没有输出语句来调试,很 ...
- Axiom3D:Ogre公告板集与合并批次
在上文中,我们把Ogre里的网格分解成点线面后,我们要完成一个新的功能,在点上突出显示. 得到顶点位置后,这个功能也就是一个很简单的事,最开始是每个顶点添加一个子节点,节点上添加一个圆点. forea ...
- e613. Modifying the Focus Traversal Order
JFrame frame = new JFrame(); JButton component1 = new JButton("1"); JButton component2 = n ...
- sqlmap注入常见用法一条龙 【转】
sqlmap是一个灰常强大的sql注入检测与辅助工具,但是由于没有图形界面,基本上用起来比较麻烦,导致很多人可能宁愿用havij或者是pangonlin也不愿意麻烦去翻帮助界面,我自己也是把很多语句贴 ...
- git中报unable to auto-detect email address 错误的解决拌办法
昨天刚配置好的git,今天刚要commit一些修改,就遇到了这个问题** Please tell me who you are. Run git config --global user.email ...
- CorelDRAW X7绘制可爱的卡通动漫多啦A梦
今天小编为大家分享CorelDRAW X7绘制可爱的卡通动漫多啦A梦方法,教程绘制出来的哆啦A梦非常可爱,适合新手来学习,推荐过来,来看看吧! 1.新建文件 新建A4大小横向画布,双击“矩形工具”绘制 ...
- Android上基于libgdx的游戏开发资料
本来之前想边学边写一个有关libgdx的游戏开发历程的,但是由于自己的懒惰和客观上的各种事情,一直没有搞下去.最近发现了一个大牛写的一本书<Beginning Android Games, 2n ...
- 基于HBase Hadoop 分布式集群环境下的MapReduce程序开发
HBase分布式集群环境搭建成功后,连续4.5天实验客户端Map/Reduce程序开发,这方面的代码网上多得是,写个测试代码非常容易,可是真正运行起来可说是历经挫折.下面就是我最终调通并让程序在集群上 ...
- koa2使用阿里云oss的nodejs sdk实现上传图片
nodejs实现上传图片到阿里云,自然是写成接口形式比较方便,前端监听input file的改变,把file对象传入到formData中传入后端,不能直接传入file对象,后端需要接受formData ...