内容
一级配置器和二级配置器的关系
类型萃取
allocator
__malloc_alloc_template
静态成员的类外初始化,其中函数指针尤为麻烦
空间配置器
SGI STL第一级配置器
1 2 template <int inst>class __malloc_alloc_template { };
allocate()直接使用malloc(),deallocate()直接使用free()。
模拟C++的set_new_handler() 以处理内存不足的情况。
SGI STL第二级配置器
1 2 template <bool threads, int inst>class __default_alloc_template { };
维护16个自由串列,负责16种小型区块的此配置能力。内存池以malloc配置而得。如果内存不足,转第一级配置器进行oom处理。
如果需求区块大于128bayes,转第一级配置器(直接malloc)。
类型萃取
iterator_traits负责萃取迭代器的特性,而SGI的__type_traits把这种技法进一步扩大到迭代器以外的世界。
我们对类型关注的属性有:
has_trivial_default_constructor
has_trivial_copy_constructor
has_trivial_assignment_operator
has_trivial_destructor
is_POD_type
如果某种类型的构造、拷贝、赋值、析构函数是不必要的,那么我们在对这个类型进行以上动作时,就可以采用最原始化、最有效率的措施,如malloc、memcpy等。这就是对类型属性萃取的意义。
1 2 struct __true_type {};struct __false_type {};
小试牛刀
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 #pragma once namespace xcg{ struct __true_type {};struct __false_type {};template <class T >struct __type_traits { typedef __true_type this_dummy_member_must_be_first; typedef __false_type has_trivial_default_constructor; typedef __false_type has_trivial_copy_constructor; typedef __false_type has_trivial_assignment_operator; typedef __false_type has_trivial_destructor; typedef __false_type is_POD_type; }; template <>struct __type_traits <char >{ typedef __true_type has_trivial_default_constructor; typedef __true_type has_trivial_copy_constructor; typedef __true_type has_trivial_assignment_operator; typedef __true_type has_trivial_destructor; typedef __true_type is_POD_type; }; template <>struct __type_traits <int >{ typedef __true_type has_trivial_default_constructor; typedef __true_type has_trivial_copy_constructor; typedef __true_type has_trivial_assignment_operator; typedef __true_type has_trivial_destructor; typedef __true_type is_POD_type; }; template <class T >struct __type_traits <T*>{ typedef __true_type has_trivial_default_constructor; typedef __true_type has_trivial_copy_constructor; typedef __true_type has_trivial_assignment_operator; typedef __true_type has_trivial_destructor; typedef __true_type is_POD_type; }; }
allocator
STL处理内存的三大部分
STL的allocator把分配空间、释放空间分工了。分配是alloc::allocate()负责,释放是alloc::deallocate()负责;对象构建是::construct()负责,对象析构是::delete()负责,new和delete往往是全局函数。
STL标准规格的配置器定义于<memory>中,SGI<memory>内含以下两个文件:
1 2 #include <stl_alloc.h> #include <stl_construct.h>
实际上,STL除了以上两个重要部分,还有stl_uninitialized.h,是处理大块内存的。
这个切入点,就可以直接把话题导向内存池了,我们可以根据具体类型的构造、析构、赋值函数是否无关紧要而采取最有效率的措施。
空间的配置与释放std::alloc
对象构建前的空间配置、对象析构后的空间释放,两个动作都由<stl_alloc.h>负责。SGI对此的设计哲学如下:
向system heap申请空间;
考虑多线程状态;
考虑内存不足时的应对措施;
考虑内存碎片问题
对于第四个问题——内存碎片问题,SGI设计了两级配置器。第一级是对于足够大的内存块处理方式。第二级是对于太小的内存块处理方式。这个大小的标准以128bytes为界。
一级配置器:
直接malloc
直接free
模拟c++的set_new_handler处理内存不足情况
二级配置器:
维护16个自由链表(free lists),内存池以malloc获取,内存不足则用一级配置器处理。
如果需求区块大于128bytes,转调用一级配置器。
一级配置器的静态成员及其初始化
先以一个简单的例子说明静态成员的类外初始化。
1 2 3 4 5 6 7 8 9 10 11 class Object { private : static int num; static void (*pfun) () ; }; int Object::num = 0 ;void (* Object::pfun)() = nullptr ;
其中,template<int inst>代表“非类型”模板,模板中的参数只是用于标识的。
1 2 3 4 5 6 7 8 9 10 11 template <int inst>class Object { private : static int num; static void (*pfun) () ; }; template <int inst>int Object<inst>::num = 0 ;template <int inst>void (* Object<inst>::pfun) () = nullptr ;
alloc类的静态成员
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 template <int inst>class __malloc_alloc_template { private : static void * oom_malloc (size_t n) ; static void * oom_realloc (void * p, size_t new_sz) ; static void (*__malloc_alloc_oom_handler) () ; public : static void * allocate (size_t n) ; static void deallocate (void * p, size_t n) ; static void * reallocate (void * p, size_t old_sz, size_t new_sz) ; static void (* set_malloc_handler(void (*f)()) ) () ; }; template <int inst>void (* __malloc_alloc_template<inst>::__malloc_alloc_oom_handler) () = nullptr ;
为了解决函数指针类型的可读性问题,使用typedef或using重定义类型名字。瞬间清爽了许多。
1 2 3 4 5 6 7 8 9 class __malloc_alloc_template { public : using PFUN = void (*)(); private : static PFUN __malloc_alloc_oom_handler; public : static PFUN set_malloc_handler (PFUN p) ; };
接下来进行类外初始化:
1 2 3 4 5 6 7 8 9 template <int inst>typename __malloc_alloc_template<inst>::PFUN __malloc_alloc_template<inst>::__malloc_alloc_oom_handler = nullptr ;
一级配置器函数实现
函数成员总览:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 template <int inst>class __malloc_alloc_template { private : static void * oom_malloc (size_t n) ; static void * oom_realloc (void * p, size_t new_sz) ; static void (*__malloc_alloc_oom_handler) () ; public : static void * allocate (size_t n) ; static void deallocate (void * p, size_t n) ; static void * reallocate (void * p, size_t old_sz, size_t new_sz) ; public : using PFUN = void (*)(); static PFUN set_malloc_handler (PFUN p) ; };
oom_handler
oom_handler,指out of memory时的处理。
1 2 3 4 5 6 static PFUN set_malloc_handler (PFUN p) { PFUN old = __malloc_alloc_oom_handler; __malloc_alloc_oom_handler = p; return old; }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 static void * oom_malloc (size_t size) { void * result = NULL ; void (*my_malloc_handler)() = nullptr ; for (;;) { my_malloc_handler = __malloc_alloc_oom_handler; if (nullptr == my_malloc_handler) { __THROW_BAD_ALLOC; } my_malloc_hanler (); result = malloc (size); if (nullptr != result) { return result; } } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 static void * oom_realloc (void * p, size_t new_sz) { void * result = NULL ; void (*my_malloc_handler)() = nullptr ; for (;;) { my_malloc_handler = __malloc_alloc_oom_handler; if (nullptr == my_malloc_handler) { __THROW_BAD_ALLOC; } my_malloc_hanler (); result = realloc (p, new_sz); if (nullptr != result) { return result; } } }
allocate
1 2 3 4 5 6 7 8 9 static void * allocate (size_t size) { void * result = malloc (size); if (nullptr == result) { result = oom_malloc (size); } return result; }
对oom_handler的简要测试
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 #include "my_alloc.h" char * chunk1 = (char *)malloc (sizeof (char )*10000000 );void handler1 () { if (chunk1!=nullptr ) { free (chunk1); } chunk1 = nullptr ; xcg::malloc_alloc::set_malloc_handler (nullptr ); } int main () { xcg::malloc_alloc::set_malloc_handler (handler1); int * p = (int *)xcg::malloc_alloc::allocate (sizeof (int )*100 ); return 0 ; }
deallocate
1 2 3 4 static void deallocate (void * p, size_t n) { free (p); }
reallocate
1 2 3 4 5 6 7 8 9 static void * reallocate (void * p, size_t old_sz, size_t new_sz) { void * result = realloc (p, new_sz); if (nullptr == result) { result = oom_realloc (p, new_sz); } return result; }
二级配置器
一级配置器的问题
直接的malloc后的数据上下各有一个越界标记。上越界标记之外还有信息,包括申请了多少字节,还有next域、prev域以便连接到链表中进行内存管理。除此之外可能还有有效/失效(是否释放)的标记。
除了包装信息占空间较多之外,还有malloc时花费的时间也多。这样就造成对于比较小的对象数据在malloc时入不敷出。
结论就是:对于较小数据,适用于内存池来进行内存管理。
对于客户端申请较小空间(128bytes)的具体处理流程 :
每次配置一大块内存,并维护对应的自由链表(free-list)
下次如果再有相同大小的内存需求,直接从free-list中取出若干小块。
如果客户端释放小块内存,就由配置器回收到free-list中。所以配置器除了分配空间,还负责回收空间。
为了方便管理,SGI第二级配置器会主动按任何小块内存的内存需求量计算成8的倍数。(比如客户端要求30bytes,则计算为32bytes。
共维护16个free-lists,各自管理大小分别为8、16、24、32、40、48、56、64、72、80、88、96、104、112、120、128的小额区块。
free-lists的单结点结构如下:
1 2 3 4 5 union obj { union obj * free_list_link; char client_data[1 ]; }
总览
1 2 3 enum { __ALIGN = 8 };enum { __MAX_BYTES = 128 };enum { __NFREELISTS = __MAX_BYTES / __ALIGN};
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 template <bool threads, int inst> class __default_alloc_template { private : union obj { union obj * free_list_link; char client_data[1 ]; }; private : static obj * volatile free_list[__NFREELISTS]; static char * start_free; static char * end_free; static size_t heap_size; static size_t ROUNT_UP (size_t bytes) ; static size_t FREELIST_INDEX (size_t bytes) ; static char * chunk_alloc (size_t size, int & nobjs) ; static void * refill (size_t size) ; public : static void * allocate (size_t size) ; static void deallocate (void * p, size_t n) ; static void * reallocate (void * p, size_t old_sz, size_t new_sz) ; };
成员初始化
1 2 3 4 5 6 7 8 9 10 11 12 13 template <bool threads, int inst> typename __default_alloc_template<threads, inst>::obj * volatile __default_alloc_template<threads, inst>::free_list[__NFREELISTS] = {}; template <bool threads, int inst>char * __default_alloc_template<threads, inst>::start_free = nullptr ;template <bool threads, int inst>char * __default_alloc_template<threads, inst>::end_free = nullptr ;template <bool threads, int inst>size_t __default_alloc_template<threads, inst>::heap_size = 0 ;
函数实现
准备工作
1 using malloc_alloc = __malloc_alloc_template<0 >;
allocate
相当于单链表的头删法,返回删了的节点的地址给用户用。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 static void * allocate (size_t size) { if (size > (size_t )__MAX_BYTES) { return malloc_alloc::allocate (size); } obj * result = nullptr ; obj * volatile * my_free_list = nullptr ; my_free_list = free_list + FREELIST_INDEX (size); result = *my_free_list; if (nullptr == result) { void * r = refill (ROUND_UP (size)); return r; } *my_free_list = result->free_list_link; return result; }
refill
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 static void * refill (size_t size) { int nobjs = 20 ; char * chunk = chunk_alloc (size, nobjs); if (1 == nobjs) { return chunk; } obj * volatile * my_free_list = NULL ; obj * result = (obj*)chunk; obj * current_obj = NULL , * next_obj = NULL ; my_free_list = free_list + FREELIST_INDEX (size); *my_free_list = next_obj = (obj*)(chunk+size); for (int i = 1 ; ; ++i) { current_obj = next_obj; next_obj = (obj*)((char *)next_obj + size); if (i == nobjs-1 ) { current_obj->free_list_link = NULL ; break ; } current_obj->free_list_link = next_obj; } return result; }
chunk_alloc
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 static char * chunk_alloc (size_t size, int & nobjs) { char * result = NULL ; size_t total_bytes = size * 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 >= size) { nobjs = bytes_left / size; total = size * nobjs; result = start_free; 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 * volatile * my_free_list = free_list + FREELIST_INDEX (bytes_left); ((obj*)start_free)->free_list_link = *my_free_list; *my_free_list = (obj*)start_free; } start_free = (char *)malloc (bytes_to_get); if (NULL == start_free) { obj * volatile * my_free_list = NULL ; for (int i = size; i <= __MAX_BYTES; i += __ALIGN) { my_free_list = free_list + FREELIST_INDEX (i); obj * p = NULL ; if (NULL != p) { *my_free_list = (*my_free_list)->free_list_link; start_free = (char *)p; end_free = start_free + i; return chunk_alloc (size, nobjs); } } start_free = malloc_alloc::allocate (bytes_to_get); } end_free = start_free + bytes_to_get; heap_size += bytes_to_get; return chunk_alloc (size, nobjs); } }
ROUND_UP & INDEX
1 2 3 4 5 6 7 8 9 10 static size_t ROUNT_UP (size_t bytes) { return (bytes+ __ALIGN-1 ) & ~(__ALIGN - 1 ); } static size_t FREELIST_INDEX (size_t bytes) { return ((bytes+ __ALIGN-1 ) / __ALIGN) - 1 ; }
测试allocate
1 2 3 4 5 6 #ifdef __USE_MALLOC typedef __malloc_alloc_template<0 > malloc_alloc;typedef malloc_alloc alloc;#else typedef __default_alloc_template<0 , 0 > alloc;#endif
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 template <class T , class Alloc >class simple_alloc { public : static T* allocate (size_t n) { return Alloc::allocate (sizeof (T)*n); } static T* allocate () { return Alloc::allocate (sizeof (T)); } void deallocate (T *p, size_t n) { if (NULL == p)return ; Alloc::deallocate (p, sizeof (T)*n); } void deallocate (T* p) { if (NULL == p)return ; Alloc::deallocate (p, sizeof (T)); } };
引入list进行测试
1 2 3 4 5 6 7 8 9 10 11 12 #include "my_list.h" using namespace std;int main () { xcg::my_list<char > mylist; for (int i = 0 ; i < 23 ; ++i) { mylist.push_back (i + 'a' ); } return 0 ; }
list构建流程:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 template <class _Ty , class _A = xcg::alloc>class my_list{ public : typedef _Ty value_type; typedef _Ty& reference; typedef _Ty* pointer; typedef const _Ty& const_reference; typedef const _Ty* const_pointer; typedef _A allocator_type; typedef xcg::simple_alloc<_Node, _A> data_allocate; public : my_list () : _Head(_Buynode()), _Size(0 ) {} protected : _Nodeptr _Buynode(_Nodeptr _parg = NULL , _Nodeptr _narg = NULL ) { _Nodeptr _S = data_allocate::allocate (); _Acc::_Prev(_S) = _parg == NULL ? _S : _parg; _Acc::_Next(_S) = _narg == NULL ? _S : _narg; return _S; } private : _Nodeptr _Head; size_t _Size; };
deallocate
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 static void deallocate (void * p, size_t size) { if (size > (size_t )__MAX_BYTES) { malloc_alloc::deallocate (p, size); return ; } obj * q = (obj*)p; obj * volatile * my_free_list = free_list + FREELIST_INDEX (size); q->free_list_link = *my_free_list; *my_free_list = q; return ; }
测试deallocate
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 iterator erase (iterator _P) { _Nodeptr _S = _P++._Mynode(); _Acc::_Next(_Acc::_Prev(_S)) = _Acc::_Next(_S); _Acc::_Prev(_Acc::_Next(_S)) = _Acc::_Prev(_S); destroy (&_Acc::_Value(_S)); _Freenode(_); return _P; } void _Freenode(_Nodeptr _P){ data_allocate::deallocate (_P); } void pop_back () { erase (--end ()); } void pop_front () { erase (begin ()); }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 class Object { private : int value; public : Object (int x = 0 ) : value (x) { cout << "create object" << this << endl; } ~Object () { cout << "destroy object" << this << endl; } }; int main () { xcg::my_list<Object> objlist; for (int i = 0 ; i < 10 ; ++i) { objlist.push_back (Object (i)); } objlist.pop_back (); return 0 ; }
reallocate
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 static void * reallocate (void * p, size_t old_sz, size_t new_sz) { if (old_sz > (size_t )__MAX_BYTES && new_sz > (size_t )__MAX_BYTES) { return malloc_alloc::reallocate (p, old_sz, new_sz); } if (ROUND_UP (old_sz) == ROUND_UP (new_sz)) { return p; } size_t sz = old_sz < new_sz ? old_sz : new_sz; void * s = allocate (new_sz); memmove (s, p ,sz); deallocate (p, old, sz); return s; }