1.空间分配器的标准接口
通常,C++内存分配和释放的操作如下:
class Foo {...};
Foo *pf = new Foo;
delete pf;
- new内含2阶段操作:
- 调用::operator new分配内存
- 调用构造函数构造对象
- delete也含2阶段操作:
- 调用析构函数析构对象
- 调用::operator delete释放内存
STL allocator将new和delete的2阶段操作进行了分离:
- 内存分配:由alloc::allocate()负责
- 内存释放:由alloc::deallocate()负责
- 对象构造:由alloc::construct()负责
- 对象析构:由alloc::destroy负责
2.SGI标准的空间分配器std::allocator
虽然SGI也定义有一个符合”部分“标准、名为allocator的分配器,但SGI自己从未用过它,也不建议我们使用。主要原因是效率不佳,只把C++的::operator new和::operator delete做一层薄薄的包装而已
std::allocator源代码
template <class T>
class allocator
{
public:
typedef T value_type;
typedef T* pointer;
typedef const T* const_pointer;
typedef T& reference;
typedef const T& const_reference;
typedef size_t size_type;
typedef ptrdiff_t difference_type;
public:
static T* allocate();
static T* allocate(size_type n);
static void deallocate(T* ptr);
static void deallocate(T* ptr, size_type n);
static void construct(T* ptr);
static void construct(T* ptr, const T& value);
static void construct(T* ptr, T&& value);
template <class... Args>
static void construct(T* ptr, Args&& ...args);
static void destroy(T* ptr);
static void destroy(T* first, T* last);
};
template <class T>
T* allocator<T>::allocate()
{
return static_cast<T*>(::operator new(sizeof(T)));
}
template <class T>
T* allocator<T>::allocate(size_type n)
{
if (n == 0)
return nullptr;
return static_cast<T*>(::operator new(n * sizeof(T)));
}
template <class T>
void allocator<T>::deallocate(T* ptr)
{
if (ptr == nullptr)
return;
::operator delete(ptr);
}
template <class T>
void allocator<T>::deallocate(T* ptr, size_type /*size*/)
{
if (ptr == nullptr)
return;
::operator delete(ptr);
}
template <class T>
void allocator<T>::construct(T* ptr)
{
mystl::construct(ptr);
}
template <class T>
void allocator<T>::construct(T* ptr, const T& value)
{
mystl::construct(ptr, value);
}
template <class T>
void allocator<T>::construct(T* ptr, T&& value)
{
mystl::construct(ptr, mystl::move(value));
}
template <class T>
template <class ...Args>
void allocator<T>::construct(T* ptr, Args&& ...args)
{
mystl::construct(ptr, mystl::forward<Args>(args)...);
}
template <class T>
void allocator<T>::destroy(T* ptr)
{
mystl::destroy(ptr);
}
template <class T>
void allocator<T>::destroy(T* first, T* last)
{
mystl::destroy(first, last);
}
3.SGI特殊的空间分配器std::alloc
STL标准规定分配器定义于中,SGI内含两个文件,负责分离的2阶段操作
真正在SGI STL中大显身手的分配器(即SGI特殊的空间分配器std::alloc)或为第一级分配器,或为第二级分配器
3.1 对象构造与析构
<stl_construct.h>
STL规定分配器必须拥有名为construct()和destroy()的两个成员函数,然而SGI特殊的空间分配器std::alloc并未遵守这一规则,所以实际上这部分属于STL allocator,但不属于std::alloc。换句话说,SGI特殊的空间分配器std::alloc不包含”3.1 对象构造与析构“,只包含”3.2 内存分配与释放“
construct.h源代码
template<class T1, class T2>
inline void construct(T1 *ptr1, const T2& value){
new(ptr1) T1(value); //palcement new 调用T1::T1(value)
}
//destroy()的第一个版本,接受一个指针
template<class T>
inline void destroy(T *ptr){
ptr->~T();
}
//destroy()的第二个版本,接受两个迭代器,此函数设法找出元素的数值型别
//进而利用__type_traits<>求取最适当的措施
template<class ForwardIterator>
inline void _destroy(ForwardIterator first, ForwardIterator last, _true_type){}
//如果元素的数值型别有 non-trival condestructor
template<class ForwardIterator>
inline void _destroy(ForwardIterator first, ForwardIterator last, _false_type){
for (; first != last; ++first){
destroy(&*first);
}
}
//判断元素的数值型憋是都有trival condestructor
template<class ForwardIterator>
inline void destroy(ForwardIterator first, ForwardIterator last){
typedef typename _type_traits<ForwardIterator>::is_POD_type is_POD_type;
_destroy(first, last, is_POD_type());
}
3.2 内存分配与释放
SGI对内存分配与释放的设计哲学如下:
- 向system heap申请空间
- 考虑多线程状态
- 考虑内存不足时的应变措施
- 考虑过多“小型区块”可能造成的内存碎片问题(SGI设计了双层级分配器)
C++的内存分配基本操作是::operator new(),内存释放基本操作是::operator delete()。这两个全局函数相当于C的malloc()和free()函数。SGI正是以malloc和free()完成内存的分配与释放
1)两级分配器
考虑到小型区块所可能造成的内存碎片问题,SGI设计了双层级分配器:
- 第一级分配器
- 直接使用malloc()和free()
- 第二级分配器
- 当分配区块超过128bytes时,视为“足够大”,调用第一级分配器
- 当分配区块小于128bytes时,视为“过小”,为了降低额外负担,采用复杂的memory pool整理方式,不再求助于第一级分配器.
第一级配置器
- 以malloc()、free()、realloc()等C函数执行实际的内存分配、释放、重分配操作
- 实现出类似C++ new-handler的机制(C++ new-handler机制是,可以要求系统在内存分配需求无法被满足时,调用一个你所指定的函数。换句话说,一旦::operator new无法完成任务,在丢出std::bad_alloc异常状态之前,会先调用由客户指定的处理例程,该处理例程通常即被称为new-handler),不能直接运用C++ new-handler机制,因为它并非使用::operator new来分配内存。
第二级配置器
第二级分配器多了一些机制,避免太多小额区块造成内存的碎片,小额区块存在下列问题:
-
产生内存碎片
-
额外负担。额外负担是一些区块信息,用以管理内存。区块越小,额外负担所比例就越大,越显浪费。
-
当区块大于128bytes时,视为大区块
- 转交第一级分配器处理
-
当区块小于128bytes时,视为小额区块
- 以内存池管理(也称为次层分配):每次分配一大块内存,并维护对应的自由链表(free-list),下次若载有相同大小的内存需求,就直接从free-list中拨出。如果客户释放小额区块,就由分配器回收到free-list中。维护有16个free-list,各自管理大小分别为8,16,24,32,40,48,56,64,72,80,88,96,104,112,120,128bytes的小额区块
- SGI第二级分配器会主动将任何小额区块的内存需求量上调至8的倍数
-
空间分配函数allocate()
- 若区块大于128bytes,就调用第一级分配器
- 若区块小于128bytes,检查对应的free-list
- 若free-list之内有可用的区块,则直接使用
- 若free-list之内没有可用区块,将区块大小调至8倍数边界,调用refill(),准备为free-list重新填充空间
-
空间释放函数deallocate()
- 若区块大于128bytes,就调用第一级分配器
- 若区块小于128bytes,找出对应的free-list,将区块回收。
*
-
重新填充free-list的函数refill()
- 若free-list中没有可用区块时,会调用chunk_alloc从内存池中申请空间重新填充free-list。缺省申请20个新节点(新区块),如果内存池空间不足,获得的节点数可能小于20。
- 如果只取了1个节点,直接分给用户,如果多于1个,分给用户一个,将剩余的加入到n对应的free_list中去。
-
chunk_alloc()函数从内存池申请空间,根据end_free-start_free判断内存池中剩余的空间
- 如果剩余空间充足
- 直接调出20个区块返回给free-list
- 如果剩余空间不足以提供20个区块,但足够供应至少1个区块
- 拨出这不足20个区块的空间
- 如果剩余空间连一个区块都无法供应
- 利用malloc()从heap中分配内存(大小为需求量的2倍,加上一个随着分配次数增加而越来越大的附加量),为内存池注入新的可用空间(详细例子见下图)
- 如果malloc()获取失败,chunk_alloc()就四处寻找有无”尚有未用且区块足够大“的free-list。找到了就挖出一块交出
- 如果上一步仍未成功,那么就调用第一级分配器,第一级分配器有out-of-memory处理机制,或许有机会释放其它的内存拿来此处使用。如果可以,就成功,否则抛出bad_alloc异常
*
- 如果剩余空间充足
上图中,一开始就调用chunk_alloc(32,20),于是malloc()分配40个32bytes区块,其中第1个交出,另19个交给free-list[3]维护,余20个留给内存池;接下来客户调用chunk_alloc(64,20),此时free_list[7]空空如也,必须向内存池申请。内存池只能供应(32*20)/64=10个64bytes区块,就把这10个区块返回,第1个交给客户,余9个由free_list[7]维护。此时内存池全空。接下来再调用chunk_alloc(96,20),此时free-list[11]空空如也,必须向内存池申请。而内存池此时也为空,于是以malloc()分配40+n(附加量)个96bytes区块,其中第1个交出,另19个交给free-list[11]维护,余20+n(附加量)个区块留给内存池…
alloc源代码
alloc.h
class alloc{
private:
enum EAlign{ ALIGN = 8};//小型区块的上调边界
enum EMaxBytes{ MAXBYTES = 128};//小型区块的上限,超过的区块由malloc分配
enum ENFreeLists{ NFREELISTS = (EMaxBytes::MAXBYTES / EAlign::ALIGN)};//free-lists的个数
enum ENObjs{ NOBJS = 20};//每次增加的节点数
private:
//free-lists的节点构造
union obj{
union obj *next;
char client[1];
};
static obj *free_list[ENFreeLists::NFREELISTS];
private:
static char *start_free;//内存池起始位置
static char *end_free;//内存池结束位置
static size_t heap_size;//
private:
//将bytes上调至8的倍数
static size_t ROUND_UP(size_t bytes){
return ((bytes + EAlign::ALIGN - 1) & ~(EAlign::ALIGN - 1));
}
//根据区块大小,决定使用第n号free-list,n从0开始计算
static size_t FREELIST_INDEX(size_t bytes){
return (((bytes)+EAlign::ALIGN - 1) / EAlign::ALIGN - 1);
}
//返回一个大小为n的对象,并可能加入大小为n的其他区块到free-list
static void *refill(size_t n);
//配置一大块空间,可容纳nobjs个大小为size的区块
//如果配置nobjs个区块有所不便,nobjs可能会降低
static char *chunk_alloc(size_t size, size_t& nobjs);
public:
static void *allocate(size_t bytes);
static void deallocate(void *ptr, size_t bytes);
static void *reallocate(void *ptr, size_t old_sz, size_t new_sz);
};
alloc.cpp
char *alloc::start_free = 0;
char *alloc::end_free = 0;
size_t alloc::heap_size = 0;
alloc::obj *alloc::free_list[alloc::ENFreeLists::NFREELISTS] = {
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
};
void *alloc::allocate(size_t bytes){
if (bytes > EMaxBytes::MAXBYTES){
return malloc(bytes);
}
size_t index = FREELIST_INDEX(bytes);
obj *list = free_list[index];
if (list){//此list还有空间给我们
free_list[index] = list->next;
return list;
}
else{//此list没有足够的空间,需要从内存池里面取空间
return refill(ROUND_UP(bytes));
}
}
void alloc::deallocate(void *ptr, size_t bytes){
if (bytes > EMaxBytes::MAXBYTES){
free(ptr);
}
else{
size_t index = FREELIST_INDEX(bytes);
obj *node = static_cast<obj *>(ptr);
node->next = free_list[index];
free_list[index] = node;
}
}
void *alloc::reallocate(void *ptr, size_t old_sz, size_t new_sz){
deallocate(ptr, old_sz);
ptr = allocate(new_sz);
return ptr;
}
//返回一个大小为n的对象,并且有时候会为适当的free list增加节点
//假设bytes已经上调为8的倍数
void *alloc::refill(size_t bytes){
size_t nobjs = ENObjs::NOBJS;
//从内存池里取
char *chunk = chunk_alloc(bytes, nobjs);
obj **my_free_list = 0;
obj *result = 0;
obj *current_obj = 0, *next_obj = 0;
if (nobjs == 1){//取出的空间只够一个对象使用
return chunk;
}
else{
my_free_list = free_list + FREELIST_INDEX(bytes);
result = (obj *)(chunk);
*my_free_list = next_obj = (obj *)(chunk + bytes);
//将取出的多余的空间加入到相应的free list里面去
for (int i = 1;; ++i){
current_obj = next_obj;
next_obj = (obj *)((char *)next_obj + bytes);
if (nobjs - 1 == i){
current_obj->next = 0;
break;
}
else{
current_obj->next = next_obj;
}
}
return result;
}
}
//假设bytes已经上调为8的倍数
char *alloc::chunk_alloc(size_t bytes, size_t& nobjs){
char *result = 0;
size_t total_bytes = bytes * nobjs;
size_t bytes_left = end_free - start_free;
if (bytes_left >= total_bytes){//内存池剩余空间完全满足需要
result = start_free;
start_free = start_free + total_bytes;
return result;
}
else if (bytes_left >= bytes){//内存池剩余空间不能完全满足需要,但足够供应一个或以上的区块
nobjs = bytes_left / bytes;
total_bytes = nobjs * bytes;
result = start_free;
start_free += total_bytes;
return result;
}
else{//内存池剩余空间连一个区块的大小都无法提供
size_t bytes_to_get = 2 * total_bytes + ROUND_UP(heap_size >> 4);
if (bytes_left > 0){
obj **my_free_list = free_list + FREELIST_INDEX(bytes_left);
((obj *)start_free)->next = *my_free_list;
*my_free_list = (obj *)start_free;
}
start_free = (char *)malloc(bytes_to_get);
if (!start_free){
obj **my_free_list = 0, *p = 0;
for (int i = 0; i <= EMaxBytes::MAXBYTES; i += EAlign::ALIGN){
my_free_list = free_list + FREELIST_INDEX(i);
p = *my_free_list;
if (p != 0){
*my_free_list = p->next;
start_free = (char *)p;
end_free = start_free + i;
return chunk_alloc(bytes, nobjs);
}
}
end_free = 0;
}
heap_size += bytes_to_get;
end_free = start_free + bytes_to_get;
return chunk_alloc(bytes, nobjs);
}
}
3.3 内存基本处理工具
STL定义了5个全局函数,作用于未初始化空间上,有助于容器的实现:
- 作用于单个对象(见3.1 对象构造与析构,SGI STL定义在头文件<stl_construct.h>中)
- construct()函数(构造单个对象)
- destroy()函数(析构单个对象)
- 作用于容器的区间(本节,SGI STL定义在头文件<stl_uninitialized.h>中,是高层copy()、fill()、fill_n()的底层函数)
- uninitialized_copy()函数
- uninitialized_fill()函数
- uninitialized_fill_n()函数
容器的全区间构造函数通常分2步:
- 分配内存区块,足以包含范围内的所有元素
- 调用上述3个函数在全区间范围内构造对象(因此,这3个函数使我们能够将内存的分配与对象的构造行为分离;并且3个函数都具有”commit or rollback“语意,要么所有对象都构造成功,要么一个都没有构造)
源代码
template<class InputIterator, class ForwardIterator>
ForwardIterator _uninitialized_copy_aux(InputIterator first, InputIterator last,
ForwardIterator result, _true_type);
template<class InputIterator, class ForwardIterator>
ForwardIterator _uninitialized_copy_aux(InputIterator first, InputIterator last,
ForwardIterator result, _false_type);
template<class InputIterator, class ForwardIterator>
ForwardIterator uninitialized_copy(InputIterator first, InputIterator last, ForwardIterator result){
typedef typename _type_traits<iterator_traits<InputIterator>::value_type>::is_POD_type isPODType;
return _uninitialized_copy_aux(first, last, result, isPODType());
}
template<class InputIterator, class ForwardIterator>
ForwardIterator _uninitialized_copy_aux(InputIterator first, InputIterator last,
ForwardIterator result, _true_type){
memcpy(result, first, (last - first) * sizeof(*first));
return result + (last - first);
}
template<class InputIterator, class ForwardIterator>
ForwardIterator _uninitialized_copy_aux(InputIterator first, InputIterator last,
ForwardIterator result, _false_type){
int i = 0;
for (; first != last; ++first, ++i){
construct((result + i), *first);
}
return (result + i);
}
/***************************************************************************/
template<class ForwardIterator, class T>
void _uninitialized_fill_aux(ForwardIterator first, ForwardIterator last,
const T& value, _true_type);
template<class ForwardIterator, class T>
void _uninitialized_fill_aux(ForwardIterator first, ForwardIterator last,
const T& value, _false_type);
template<class ForwardIterator, class T>
void uninitialized_fill(ForwardIterator first, ForwardIterator last, const T& value){
typedef typename _type_traits<T>::is_POD_type isPODType;
_uninitialized_fill_aux(first, last, value, isPODType());
}
template<class ForwardIterator, class T>
void _uninitialized_fill_aux(ForwardIterator first, ForwardIterator last,
const T& value, _true_type){
fill(first, last, value);
}
template<class ForwardIterator, class T>
void _uninitialized_fill_aux(ForwardIterator first, ForwardIterator last,
const T& value, _false_type){
for (; first != last; ++first){
construct(first, value);
}
}
/***************************************************************************/
template<class ForwardIterator, class Size, class T>
ForwardIterator _uninitialized_fill_n_aux(ForwardIterator first,
Size n, const T& x, _true_type);
template<class ForwardIterator, class Size, class T>
ForwardIterator _uninitialized_fill_n_aux(ForwardIterator first,
Size n, const T& x, _false_type);
template<class ForwardIterator, class Size, class T>
inline ForwardIterator uninitialized_fill_n(ForwardIterator first,
Size n, const T& x){
typedef typename _type_traits<T>::is_POD_type isPODType;
return _uninitialized_fill_n_aux(first, n, x, isPODType());
}
template<class ForwardIterator, class Size, class T>
ForwardIterator _uninitialized_fill_n_aux(ForwardIterator first,
Size n, const T& x, _true_type){
return fill_n(first, n, x);
}
template<class ForwardIterator, class Size, class T>
ForwardIterator _uninitialized_fill_n_aux(ForwardIterator first,
Size n, const T& x, _false_type){
int i = 0;
for (; i != n; ++i){
construct((T*)(first + i), x);
}
return (first + i);
}