文章目录
背景
1无论是C的malloc还是C++封装malloc的new,在向堆申请空间,都是没有规章的动态申请内存,即不关心已申请的内存块与自己的相对位置,这也就会导致内存的浪费,尤其是在一些频繁申请内存的地方,OS需要重新分配资源调度,运行程序运行效率
mallco和new缺点
- 空间申请与释放需要用户自己管理,容易造成内存泄漏
- 频繁向系统申请小块内存块,容易造成内存碎片
- 频繁向系统申请小块内存,影响程序运行效率
- 直接使用malloc与new进行申请,每块空间前有额外空间浪费
- 申请空间失败怎么应对
- 代码结构比较混乱,代码复用率不高
- 未考虑线程安全问题
SGI-STL空间配置器
基于malloc和new的不足,STL设计一块高效的内存管理机制专门 用于解决频繁申请小块内存导致的问题即空间配置器。
内存池
内存池就是:先申请一块比较大的内存块已做备用,当需要内存时,直接到内存池通过空间配置器进行管理,当池中空间不够时,再向内存中去取,当用户不用时,直接还回内存池即可。避免了频繁向系统申请小块内存所造成的效率低、内存碎片以及额外浪费的问题
alloc工作原理
首先alloc会向堆申请一大块内存(大小基于不同编译器)用于给容器分配资源。如果一次性申请的内存大于128字节,会采用一级空间配置器(其实就是mallco和new),如果一次申请内存小于128字节会采用二级空间配置器—这是内存池的精华
一级空间配置器
本质上就是malloc和new
二级空间配置器
-
采用哈希桶的逻辑进行管理,但是不同的是:不同桶中的元素大小是不一样的。
-
向内存池申请小块内存块,通过链表的头插,头删高效的实现内存的申请与释放,
-
二级空间配置器采用8字节对齐。
union obj { union obj * free_list_link; char client_data[1]; /* The client sees this. */ };
- 对于桶中的元素,二级空间配置器将前4字节(32位)/8字节(64位)的内存存放next指针,头删后next指针无效了,内存块会覆盖这个内存,实现对应字节内存块的申请,当释放内存块时,通过头插,前4/8字节继续存放next指针,这就实现了next空间的2用,且不会产生任何影响
- 这也就是为什么采用8字节对齐而不是4字节对齐,已64位下系统考虑
-
每个桶都有其对应的内存块大小,以8开始,以8为差到128的等比数列
源码分析
空间配置器的再次封装
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)); }
};
对象的构造与释放
一切为了效率考虑,SGI-STL决定将空间申请释放和对象的构造析构两个过程分离开,因为有些对象的构造不需要调用析构函数,销毁时不需要调用析构函数,将该过程分离开可以提高程序的性能.
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));
}
my_free_list = free_list + FREELIST_INDEX(n);
// Acquire the lock here with a constructor call.
// This ensures that it is released in exit or during stack
// unwinding.
# ifndef _NOTHREADS
/*REFERENCED*/
lock lock_instance;
# endif
result = *my_free_list;
if (result == 0) {
void *r = refill(ROUND_UP(n));
return r;
}
*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);
return;
}
my_free_list = free_list + FREELIST_INDEX(n);
// acquire lock
# ifndef _NOTHREADS
/*REFERENCED*/
lock lock_instance;
# endif /* _NOTHREADS */
q -> free_list_link = *my_free_list;
*my_free_list = q;
// lock is released here
}
外碎片问题
malloc/new直接到堆频繁申请小块导致的空间浪费称为外碎片
内碎片问题
二级空间配置的桶元素大小是固定的,产生的空间浪费称为内碎片问题,在有规章的二级空间配置器控制下,这种浪费是可接受,无伤大雅的。
STL—list的空间配置器
一般通过结点 实现的容器都是通过二级空间配置器实现频繁的小块内存的控制。
STL—vector的空间配置器
-
vetctor不同于list那种通过结点单独申请空间
-
vector通过申请连续的空间,这意味当发生扩容时,会改变再哈希桶的位置。当扩容超出128字节时,会通过深拷贝有一级空间配置器控制,同时旧空间会回收到内存池。
-
源码这里就不介绍了和list相似