SGI的特殊空间配置器
SGI的空间配置器allocator只是简单的new和delete的一层包装,没有提供效率的强化。
而一般C++内存配置和释放操作如下:
class Foo { ... }
Foo *pf = new Foo;
delete pf;
new算式:1)使用new配置内存,2)使用Foo构造对象
delelte算式: 1)使用~Foo()将对象析构 ,2)使用delete释放内存
STL allocator 将这两阶段操作区分开来。内存配置操作由 alloc::allocate() 负责,内存释放操作由 alloc::deallocate() 负责;对象构造操作由 ::construct() 负责,对象析构操作由 ::destroy() 负责。
如下图是STL allocator的构造:
| <stl_constuct.h> //全局函数:constrcut(),destory()负责对象的构造和析构
<memory>----- |<stl_alloc.h> // 一、二级配置器(alloc)
|<stl_uninitialized.h> //全局函数:un_initialized_copy()、un_initialized_fill()、un_initialized_fill_n() 复制填充大块内存数据
//这些函数会考虑效率,最差用constrcut(),最佳时用memmove()直接进行内存移动
一、<stl_constuct.h>
constrcut()
template<class T1,class T2> inline void construct(T1*p,const T2&value ) { new (p) T1(value); }
destory()
template<class T>//版本一 inline void destroy(T* pointer) { pointer->~T(); } template<class ForwardIterator,class T>//版本二 inline void destroy(ForwardIterator first,ForwardIterator last,T*) { typedef typename _type_traits<T>::has_trivial_destructor tivial_destructor(); _destory_aux(first,last,trivial_destructor); }
版本二会根据数值型别判断,如果是_true_type就什么也不做, _false_type会循环每个对象依次调用版本一的destroy
二、<stl_alloc.h>
SGI对此的设计哲学如下:
1. 向 system heap 要求空间。
2. 考虑多线程(multi-threads)状态。
3. 考虑内存不足时的应变措施。
4. 考虑过多“小型区块”可能造成的内存碎片(fragment)问题。
为了处理小型区块可能造成的内存碎块,SGI设计两层配置器,如下:
第一级: (大于128bytes) 第二级
template <int inst> template<bool threads,int inst>
class _malloc_alloc_template{..} class _default_alloc_template{..};
其中: 其中:
1、allocate()直接使用malloc() 1、维护16个 free lists,负责16种小型区块的次配置能力
deallocate()直接使用free() 内存池(memory pool)以malloc配置而得,内存不足转第一级配置器
2、模拟C++的set_new_handler()处理内存不足 2、需求区块大于128bytes,转第一级配置器
由于第一级,第二级只是以128bytes判断,所以需要用simple_alloc接口进行封装,使得由bytes转为元素大小。
下面看看第一级配置器的内存不足处理
template <int inst> class __malloc_alloc_template { private://处理内存不足函数 static void *oom_malloc(size_t); static void *oom_realloc(void *, size_t); static void (* __malloc_alloc_oom_handler)(); static void (* set_malloc_handler(void (*f)()))() { void (* old)() = __malloc_alloc_oom_handler; __malloc_alloc_oom_handler = f; return(old); } } template <int inst> void * __malloc_alloc_template<inst>::oom_malloc(size_t n) { void (* my_malloc_handler)(); void *result; for (;;) { // 不断尝试释放、配置、再释放、再配置... my_malloc_handler = __malloc_alloc_oom_handler; if (0 == my_malloc_handler) { __THROW_BAD_ALLOC; } (*my_malloc_handler)(); // 调用处理例程,企图释放内存 result = malloc(n); // 再次尝试配置内存 if (result) return(result); } } template <int inst> void * __malloc_alloc_template<inst>::oom_realloc(void *p, size_t n) { void (* my_malloc_handler)(); void *result; for (;;) { // 不断尝试释放、配置、再释放、再配置... my_malloc_handler = __malloc_alloc_oom_handler; if (0 == my_malloc_handler) { __THROW_BAD_ALLOC; } (*my_malloc_handler)(); // 调用处理例程,企图释放内存 result = realloc(p, n); // 再次尝试配置内存 if (result) return(result); } }
第二级配置器:
当区块小于128bytes,每次配置一大块内存,并维护其对应的自由链表,配置器负责分配与回收。
free-lists节点结构:
为了尽最大可能减少内存的使用, 这里使用一个union ,如果使用第一个成员, 则指向另一个相同的union obj ,如果使用第二个成员, 则指向实际的内存区域 ,这样就实现了链表结点只使用一个指针的大小空间, 却能同时做索引和指向内存区域!!!!!
union obj { union obj * free_list_link; char client_data[1]; /* The client sees this. */ };
第二级配置器中主要有
ROUND_UP(x):负责将输入的字节数上调至8的倍数,如下:
(x+7)&~7 // +7是上升一个8的倍数,然后~7是清除/8的余数
allocate(): 首先判断区块大小,大于128就调用第一级,小于128时就检查 free list, free list里有可用的区块就直接用,若没有就将区块大小上升至8的倍数,然后用refill()为 free list重新填充空间。如下:
static void * allocate(size_t n) { obj * __VOLATILE * my_free_list; obj * __RESTRICT result; // 大于128 就调用第一级配置器 if (n > (size_t) __MAX_BYTES) { return(malloc_alloc::allocate(n)); } // 寻找 16 个free lists中适当的一个 my_free_list = free_list + FREELIST_INDEX(n); #ifndef _NOTHREADS /*REFERENCED*/ lock lock_instance; #endif result = *my_free_list; if (result == 0) { // 没找到可用的 free list,准备重新填充 free list void *r = refill(ROUND_UP(n)); return r; } // 调整 free list *my_free_list = result -> free_list_link; return (result); };
my_free_list = free_list + FREELIST_INDEX(n); //my_free_list会定位到数组16个元素的某一个(里面存的是对应链表头的地址)
result = *my_free_list; //result 是一个指针,会指向对应的链表头
deallocate(): 首先判断区块大小,大余128就调用第一级配置器,小于128就找相应的freelist,将区块回收
static void deallocate(void *p, size_t n) //p是待回收的内存地址,n是对应freelist 要回收的地方 { obj *q = (obj *)p; obj * __VOLATILE * my_free_list; // 大于 128 就调用第一级配置器 if (n > (size_t) __MAX_BYTES) { malloc_alloc::deallocate(p, n); return; } // 寻找对应的 free list my_free_list = free_list + FREELIST_INDEX(n); // acquire lock #ifndef _NOTHREADS /*REFERENCED*/ lock lock_instance; #endif /* _NOTHREADS */ // 调整 free list,回收区块 q -> free_list_link = *my_free_list; *my_free_list = q; //把这段待放的内存块插入链表头 // lock is released here }
refill():发现freelist没有可用空间时,从内存池(chunk_alloc()完成),缺省取20,内存池不足时,可能小于20.
// 返回一个大小为 n 的对象,并且有时候会为适当的 free list 增加节点 // 假设 n 已经适当上调至 8 的倍数 /* We hold the allocation lock. */ template <bool threads, int inst> void* __default_alloc_template<threads, inst>::refill(size_t n) { int nobjs = 20; // 调用 chunk_alloc(),尝试取得 nobjs 个区块作为 free list 的新节点 // 注意参数 nobjs 是 pass by reference char * chunk = chunk_alloc(n, nobjs); obj * __VOLATILE * my_free_list; obj * result; obj * current_obj, * next_obj; int i; // 如果只获得一个区块,这个区块就分配给调用者用,free list无新节点 if (1 == nobjs) return(chunk); // 否则准备调整 free list,纳入新节点 my_free_list = free_list + FREELIST_INDEX(n); // 以下在 chunk 空间内建立 free list result = (obj *)chunk; // 这一块准备返回给客户端 // 以下导引 free list 指向新配置的空间(取自内存池) *my_free_list = next_obj = (obj *)(chunk + n); // 以下将 free list的各节点串接起来 for (i = 1; ; i++) { // 从 1 开始,因为第 0 个将返回给客端 current_obj = next_obj; next_obj = (obj *)((char *)next_obj + n); //切割成一个个小块 if (nobjs - 1 == i) { current_obj -> free_list_link = 0; break; } else { current_obj -> free_list_link = next_obj; } } return(result); }
chunk_alloc():是从内存池取内存给freelist的。如果有足够块数就直接返回,不足时就返回整数块数。如果连一块都不足时,首先考虑将内存池剩余空间编入freelist,然后向堆栈申请空间,连堆栈都没有空间时,再回freelist里找没用到且区块够大的,最后还不行才抛异常。
// 假设 size 已经适当上调至 8 的倍数 // 注意参数 nobjs 是 pass by reference template <bool threads, int inst> char* __default_alloc_template<threads, inst>::chunk_alloc(size_t size, int& nobjs) { char * result; size_t total_bytes = size * nobjs; size_t bytes_left = end_free - start_free; // 内存池剩余空间 if (bytes_left >= total_bytes) { // 内存池剩余空间完全满足需求量 result = start_free; start_free += total_bytes; return(result); } else if (bytes_left >= size) { // 内存池剩余空间不能完全满足需求量,但足够供应一个(含)以上的区块 nobjs = bytes_left/size; total_bytes = size * nobjs; result = start_free; start_free += total_bytes; return(result); } else { // 内存池剩余空间连一个区块的大小都无法提供 size_t bytes_to_get = 2 * total_bytes + ROUND_UP(heap_size >> 4); // 以下试着让内存池中的残余零头还有利用价值(零头也应该是 8 的倍数) if (bytes_left > 0) { // 内存池内还有一些零头,先配给适当的free list // 首先寻找适当的 free list obj * __VOLATILE * my_free_list = free_list + FREELIST_INDEX(bytes_left); // 调整 free list,将内存池中的残余空间编入 ((obj *)start_free) -> free_list_link = *my_free_list; *my_free_list = (obj *)start_free; } // 配置 heap 空间,用来补充内存池 start_free = (char *)malloc(bytes_to_get); if (0 == start_free) { // heap空间不足,malloc()失败 int i; obj * __VOLATILE * my_free_list, *p; // Try to make do with what we have. That can't // hurt. We do not try smaller requests, since that tends // to result in disaster on multi-process machines. // 试着检视我们手上拥有的东西。这不会造成伤害。我们不打算尝试配置 // 较小的区块,因为那在多进程(multi-process)机器上容易导致灾难 // 以下搜寻适当的 free list // 所谓适当是指"尚有未用区块,且区块够大"之 free list for (i = size; i <= __MAX_BYTES; i += __ALIGN) { my_free_list = free_list + FREELIST_INDEX(i); p = *my_free_list; if (0 != p) { // free list内尚有未用区块 // 调整free list以释出未用区块 *my_free_list = p -> free_list_link; start_free = (char *)p; end_free = start_free + i; // 递归调用自己,为了修正 nobjs return(chunk_alloc(size, nobjs)); // 注意,任何残余零头终将被编入适当的free-list中备用 } } end_free = 0; // 如果出现意外,到处都没内存可用 // 调用第一级配置器,看看 out-of-memory 机制能否尽点力 start_free = (char *)malloc_alloc::allocate(bytes_to_get); // 这会导致抛出异常(exception),或内存不足的情况获得改善 } heap_size += bytes_to_get; end_free = start_free + bytes_to_get; // 递归调用自己,为了修正 nobjs return(chunk_alloc(size, nobjs)); } }