您的位置:首页 > 其它

SGI STL 空间配置器(allocator)源码剖析

2014-08-28 14:36 204 查看

空间配置器的作用

我们知道,stl中的数据都是放到容器中的,容器需要存储空间,空间配置器就是负责容器存储空间的分配、回收等一系列内存操作。

STL标准规范中描述的allocator模版类的必要声明:

[cpp] view
plaincopy

<strong>Constructors</strong>

// Constructors used to create allocator objects.

allocator(); // Default constructor

allocator(const allocator<Type>& _Right); // Copy constructor

template<class Other> // Template copy constructor

allocator(const allocator<Other>& _Right);

<strong>Typedefs</strong>

const_pointer // A type that provides a constant pointer to the type of object managed by the allocator.

const_reference // A type that provides a constant reference to type of object managed by the allocator.

difference_type // A signed integral type that can represent the difference between values of pointers to the type of object managed by the allocator.

pointer // A type that provides a pointer to the type of object managed by the allocator.

reference // A type that provides a reference to the type of object managed by the allocator.

size_type // An unsigned integral type that can represent the length of any sequence that an object of template class allocator can allocate.

value_type // A type that is managed by the allocator.

<strong>Member Functions</strong>

// Finds the address of an object whose value is specified.

pointer (reference _Val) const;

const_pointer (const_reference _Val) const;

// Allocates a block of memory large enough to store at least some specified number of elements.

pointer allocate(size_type _Count, const void* _Hint);

// Constructs a specific type of object at a specified address that is initialized with a specified value.

void construct(pointer _Ptr, const Type& _Val);

// Frees a specified number of objects from storage beginning at a specified position.

void deallocate(pointer _Ptr, size_type _Count);

// Calls an objects destructor without deallocating the memory where the object was stored.

void destroy(pointer _Ptr);

// Returns the number of elements of type Type that could be allocated by an object of class allocator before the free memory is used up.

size_type max_size( ) const;

// A structure that enables an allocator for objects of one type to allocate storage for objects of another type.

template<class _Other>

struct rebind {

typedef allocator<_Other> other;

};

<strong>Operators overload</strong>

template<class Other>

allocator<Type>& operator=(const allocator<Other>& _Right); // Assignment operator

SGI STL使用的空间配置器

SGI STL底层没有采用上述STL标准规范中的声明,它的配置器与众不同,其名称是alloc而非allocator,而且不接受任何参数。
SGI STL中的每种容器都已经指定其缺省的空间配置器alloc,我们在使用时很少需要自行指定配置器。
SGI STL空间配置器的设计哲学:

从堆中申请内存空间
考虑多线程状态
考虑内存不足时的应变措施
考虑过多“小型区块”可能造成的内存碎片问题

SGI STL空间配置器alloc的设计描述:

使用双层级配置器
第一级配置器(__malloc_alloc_template)直接使用malloc() 和 free()
第二级配置器(__default_alloc_template)视情况采用不同策略:当配置区块超过128byte时,视之为“足够大”,便调用第一级配置器;否则,视之为“过小”,为了提高效率(1.减少内存碎片;2.降低额外负担:直接使用malloc()申请内存时,系统会要多分配一些内存(内存头)用来管理所分配的内存空间),采用复杂的内存池(memory pool)技术(见下文详解),不再求助于第一级配置器
SGI STL为了使配置器的接口能够符合STL的标准规范,提供了一个包装接口simple_alloc:

[cpp] view
plaincopy

template<class T, class Alloc>

class simple_alloc {

public:

static T *allocate(size_t n)

{ return 0 == n? 0 : (T*) Alloc::allocate(n * sizeof (T)); }

static T *allocate(void)

{ return (T*) Alloc::allocate(sizeof (T)); }

static void deallocate(T *p, size_t n)

{ if (0 != n) Alloc::deallocate(p, n * sizeof (T)); }

static void deallocate(T *p)

{ Alloc::deallocate(p, sizeof (T)); }

};

第一级配置器__malloc_alloc_template源码剖析



[cpp] view
plaincopy

<span style="font-size:10px;">// 基于malloc()的配置器.通常比第二级配置器(__default_alloc_template)慢.

// 通常是线程安全的,并且对存储空间的使用更加高效.

#ifdef __STL_STATIC_TEMPLATE_MEMBER_BUG

# ifdef __DECLARE_GLOBALS_HERE

void (* __malloc_alloc_oom_handler)() = 0; // 内存不足处理函数指针

// g++ 2.7.2 does not handle static template data members.

# else

extern void (* __malloc_alloc_oom_handler)();

# endif

#endif

template <int inst> // 没有模版型别参数,至于非型别参数inst没有用到

class __malloc_alloc_template {

private: // 用来处理内存不足的情况

static void *oom_malloc(size_t);

static void *oom_realloc(void *, size_t);

#ifndef __STL_STATIC_TEMPLATE_MEMBER_BUG

static void (* __malloc_alloc_oom_handler)();

#endif

public:

static void * allocate(size_t n)

{

void *result = malloc(n); // 直接调用malloc()

if (0 == result)

result = oom_malloc(n); // 内存申请失败,调用内存不足处理函数

return result;

}

static void deallocate(void *p, size_t /* n */)

{

free(p); // 直接调用free()

}

static void * reallocate(void *p, size_t /* old_sz */, size_t new_sz)

{

void * result = realloc(p, new_sz); // 直接使用realloc()

if (0 == result)

result = oom_realloc(p, new_sz); // 内存申请失败,调用内存不足处理函数

return result;

}

// 指定内存不足处理函数句柄

static void (* set_malloc_handler(void (*f)()))()

{

void (* old)() = __malloc_alloc_oom_handler;

__malloc_alloc_oom_handler = f;

return(old);

}

};

#ifndef __STL_STATIC_TEMPLATE_MEMBER_BUG

template <int inst>

void (* __malloc_alloc_template<inst>::__malloc_alloc_oom_handler)() = 0; // 初值为0,需client设定

#endif

// 内存不足处理函数:malloc()申请内存失败

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; } // client未设定处理函数,直接抛出异常

(*my_malloc_handler)(); // 调用内存不足处理函数

result = malloc(n); // 再次尝试申请内存

if (result)

return(result); // 申请成功

}

}

// 内存不足处理函数:realloc()申请内存失败(与oom_malloc()类似)

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);

}

}

typedef __malloc_alloc_template<0> malloc_alloc; // inst直接被指定为0</span>

总结:

第一层配置器比较简单,直接调用相应的C函数
并实现出类似C++ new-handler机制,来处理内存不足的情况

第二级配置器__default_alloc_template源码剖析

[cpp] view
plaincopy

// 多线程搞不懂,故去掉了线程相关代码,留待以后分析

template <bool threads, int inst>

class __default_alloc_template {

private:

// Really we should use static const int x = N

// instead of enum { x = N }, but few compilers accept the former.

// 唉,为了兼容性,考虑得太周到了

# ifndef __SUNPRO_CC

enum {__ALIGN = 8};

enum {__MAX_BYTES = 128};

enum {__NFREELISTS = __MAX_BYTES/__ALIGN};

# endif

// 内存对齐:将bytes上调至8的倍数

static size_t ROUND_UP(size_t bytes) {

return (((bytes) + __ALIGN-1) & ~(__ALIGN - 1));

}

__PRIVATE:

// free_list的节点构造

union obj {

union obj * free_list_link;

char client_data[1]; /* The client sees this. 这里没搞懂 */

};

private:

# ifdef __SUNPRO_CC

static obj * __VOLATILE free_list[];

// Specifying a size results in duplicate def for 4.1

# else

static obj * __VOLATILE free_list[__NFREELISTS]; // free_list数组,16个元素

# endif

// 根据bytes大小获取free_list的数组下标,从0开始

static size_t FREELIST_INDEX(size_t bytes) {

return (((bytes) + __ALIGN-1)/__ALIGN - 1);

}

// Returns an object of size n, and optionally adds to size n free list.

// 返回一个大小为n的对象,并可能加入大小为n的其它区块到free list中

static void *refill(size_t n);

// Allocates a chunk for nobjs of size "size". nobjs may be reduced

// if it is inconvenient to allocate the requested number.

// 配置一块空间,可容纳nobjs个大小为"size"的区块

// 如果不能配置nobjs个区块,nobjs的大小可能会减少(按引用传递的)

static char *chunk_alloc(size_t size, int &nobjs);

// 内存池状态

static char *start_free; // 内存池起始位置。只在chunk_alloc()中变化

static char *end_free; // 内存池结束位置。只在chunk_alloc()中变化

static size_t heap_size; // 从堆中申请的内存大小

public:

/* n must be > 0 */

static void * allocate(size_t n)

{

obj * __VOLATILE * my_free_list;

obj * __RESTRICT result;

if (n > (size_t) __MAX_BYTES) {

return(malloc_alloc::allocate(n)); // n大于128byte,直接调用第一级配置器

}

my_free_list = free_list + FREELIST_INDEX(n); // 从16个free_list中获取合适的一个

result = *my_free_list;

if (result == 0) {

void *r = refill(ROUND_UP(n)); // 没有可用的free_list,重新填充free_list

return r;

}

// 调整free_list:将已使用的内存区块从free_list中移除

*my_free_list = result -> free_list_link;

return (result);

};

/* p may not be 0 */

static void deallocate(void *p, size_t n)

{

obj *q = (obj *)p;

obj * __VOLATILE * my_free_list;

if (n > (size_t) __MAX_BYTES) {

malloc_alloc::deallocate(p, n); // n大于128byte,直接调用第一级配置器

return;

}

my_free_list = free_list + FREELIST_INDEX(n); // 从16个free_list中获取合适的一个

// 调整free_list: 将回收内存区块重新添加到free_list中

q -> free_list_link = *my_free_list;

*my_free_list = q;

}

static void * reallocate(void *p, size_t old_sz, size_t new_sz);

} ;

// static数据的定义与初值设定

template <bool threads, int inst>

char *__default_alloc_template<threads, inst>::start_free = 0;

template <bool threads, int inst>

char *__default_alloc_template<threads, inst>::end_free = 0;

template <bool threads, int inst>

size_t __default_alloc_template<threads, inst>::heap_size = 0;

template <bool threads, int inst>

__default_alloc_template<threads, inst>::obj * __VOLATILE

__default_alloc_template<threads, inst> ::free_list[

# ifdef __SUNPRO_CC

__NFREELISTS

# else

__default_alloc_template<threads, inst>::__NFREELISTS

# endif

] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, };

// The 16 zeros are necessary to make version 4.1 of the SunPro

// compiler happy. Otherwise it appears to allocate too little

// space for the array.

/* We allocate memory in large chunks in order to avoid fragmenting */

/* the malloc heap too much. */

/* We assume that size is properly aligned. */

/* We hold the allocation lock. */

// 为了避免堆中有过多的内存碎片,我们每次申请一大块内存空间

// 我们假定这个空间大小是内存对齐的

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; // 调整nobjs的值

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);

// 先把内存池剩余空间分配给合适的free_list

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 (0 == start_free) { // 申请内存失败

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.

// 先尝试检查我们手上(free_list)拥有的东西。这不会造成伤害。

// 我们不打算检查比size更小的区块,因为那在多进程机器上容易导致灾难。

for (i = size; i <= __MAX_BYTES; i += __ALIGN) {

my_free_list = free_list + FREELIST_INDEX(i);

p = *my_free_list;

if (0 != p) { // 存在未用区块

// 释放该未用区块

*my_free_list = p -> free_list_link;

start_free = (char *)p;

end_free = start_free + i;

return(chunk_alloc(size, nobjs)); // 递归调用自己,重新分配

// Any leftover piece will eventually make it to the

// right free list.

// 任何内存池剩余空间终将被编入适当的free list

}

}

// 55,山穷水尽了

end_free = 0;

// 调用第一级配置器,看看内存不足处理函数能不能起作用

start_free = (char *)malloc_alloc::allocate(bytes_to_get);

// This should either throw an

// exception or remedy the situation. Thus we assume it

// succeeded.

// 要么抛出异常,要么内存不足的情况得以改善,我们假定它成功了

}

heap_size += bytes_to_get;

end_free = start_free + bytes_to_get;

return(chunk_alloc(size, nobjs)); // 递归调用自己,重新分配

}

}

/* Returns an object of size n, and optionally adds to size n free list.*/

/* We assume that n is properly aligned. */

/* We hold the allocation lock. */

// 返回一个大小为n的指针对象,并且有可能会为适当的free list增加节点

// 假定n已经适当调整到8的倍数了

template <bool threads, int inst>

void* __default_alloc_template<threads, inst>::refill(size_t n)

{

int nobjs = 20; // 缺省取得20个新节点

char * chunk = chunk_alloc(n, nobjs);

obj * __VOLATILE * my_free_list;

obj * result;

obj * current_obj, * next_obj;

int i;

if (1 == nobjs)

return(chunk); // 只获得一个节点,直接返回

// 否则准备调整free list,纳入新节点

my_free_list = free_list + FREELIST_INDEX(n); // 找到合适的free list

/* Build free list in chunk */

// 纳入新节点

result = (obj *)chunk;

*my_free_list = next_obj = (obj *)(chunk + n);

for (i = 1; ; i++) {

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);

}

template <bool threads, int inst>

void*

__default_alloc_template<threads, inst>::reallocate(void *p,

size_t old_sz,

size_t new_sz)

{

void * result;

size_t copy_sz;

if (old_sz > (size_t) __MAX_BYTES && new_sz > (size_t) __MAX_BYTES) {

return(realloc(p, new_sz)); // 疑问:这里怎么不直接调用第一级配置器里面的realloc

}

if (ROUND_UP(old_sz) == ROUND_UP(new_sz)) return(p);

result = allocate(new_sz);

copy_sz = new_sz > old_sz? old_sz : new_sz;

memcpy(result, p, copy_sz);

deallocate(p, old_sz);

return(result);

}

typedef __default_alloc_template<__NODE_ALLOCATOR_THREADS, 0> alloc;

typedef __default_alloc_template<false, 0> single_client_alloc;

总结:

第二层配置器的核心就是内存池
SGI STL的内存池通过free_list数组来管理,数组中包含16个free list,每个free list节点大小依次从8、16、24递增到128byte
分配内存时(假定分配大小在128byte内),首先根据分配大小找到合适的free list,如果该free list中没有可用的节点,则调用refill()函数从内存池中取空间给该free list用,然后返回第一个节点的内存空间,并调整该free list
refill()缺省取20个新节点(区块),通过调用chunk_alloc(),然后根据取得新节点的实际个数,调整相应的free list(大于1)
chunk_alloc()内部实现最为复杂,根据内存池剩余空间大小与需求量的关系,分为三种情况,具体看代码,不再赘述

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: