我们知道STL容器在不断保存数据时,当保存的的数据个数超过容器容量时,需要进行扩容。但是,当不断保存数据时,就可能需要不断的进行扩容。此时,扩容需要不断的向操作系统申请空间,释放空间。操作系统是很繁忙的,这样会大大影响操作系统的效率,于是就出现了空间配置器
空间配置器:是操作系统开辟的一大段内存空间。STL需要扩容申请内存时,就从空间配置器中申请,不需要再经过操作系统。并且,它还能回收释放的空间,供下一次使用
空间配置器的优点:
空间配置器有两级结构,一级空间配置器是用来处理大块内存,二级空间配置器处理小块内存。SGI-STL规定以128字节作为小块内存和大块内存的分界线
为什么要把空间配置器区分为两级呢?
- 因为STL容器,一般申请的都会是小块的内存
- 二级空间配置器,主要是管理容器申请空间和释放的空间
- 如果用户申请的空间直接大于的128字节直接找的是一级空间配置器申请空间
一级空间配置器源代码:
template <int inst>
class __malloc_alloc_template
{
private:
static void *oom_malloc(size_t);
public:
// 对malloc的封装
static void * allocate(size_t n)
{
// 申请空间成功,直接返回,失败交由oom_malloc处理
void *result = malloc(n);
if (0 == result)
result = oom_malloc(n);
return result;
}
// 对free的封装
static void deallocate(void *p, size_t )
{
free(p);
}
// 模拟set_new_handle
// 该函数的参数为函数指针,返回值类型也为函数指针
// void (* set_malloc_handler( void (*f)() ) )()
static void(*set_malloc_handler(void(*f)()))()
{
void(*old)() = __malloc_alloc_oom_handler;
__malloc_alloc_oom_handler = f;
return(old);
}
};
// malloc申请空间失败时代用该函数
template <int inst>
void * __malloc_alloc_template<inst>::oom_malloc(size_t n)
{
void(*my_malloc_handler)();
void *result;
for (;;)
{
// 检测用户是否设置空间不足应对措施,如果没有设置,抛异常,模式new的方式
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);
}
}
typedef __malloc_alloc_template<0> malloc_alloc;
内存池的技术来提高申请空间的速度以及减少额外空间的浪费
,采用哈希桶的方式来提高用户获取空间的速度和高效管理
内存池技术的原理:
- 内存池就是,先申请一块较大的内存块做为备用,当需要内存时,直接从内存池中取内存,当内存池中内存不够时,使用一级空间配置器,向内存中申请大块空间。当用户不用申请的空间时,直接归还内存池
- 这样就避免了频繁向系统申请小块内存找出的效率低,内存碎片的问题
但是这样会有一个问题:如何归还给内存池?
用户申请空间很简单,直接从内存池中申请。但是,当用户释放该空间时,并不知道这块空间应该放在内存池的什么位置。归还成了一个问题,所以在STL配置器中,使用
哈希桶的技术
来解决这一问题
哈希桶技术的原理:
说明:用户申请的空间基本上都会是4的整数倍,其它空间大小几乎很少用到。所以哈希桶并不需要对1~128字节的空间进行管理,也就是并不需要128个桶。SGI-STL将用户申请的内存块向上对齐到了8字节的整数倍
为什么不用链表来管理归还的空间?
- 因为用户申请空间,查找合适的内存块时,效率低
为什么向上对齐到了8字节的整数倍?
- 因为在哈希桶下,是以链表的形式将所有内存块连接起来的。该内存块至少需要保存下一个内存块的地址,在64位系统下,为8字节
- 但是这样造成了内存碎片问题
- 用户归还的空间,会被连接到哈希桶对应空间大小的位置下
哈希桶结构:
- 大致流程: 容器进行扩容,如果申请的空间是大于128字节,直接向一级空间配置器申请。如果小于128字节,先查找哈希桶对应大小位置是否为空,不为空,直接从该位置申请空间,如果该位置为空,向内存池申请。当内存池空间不够了会直接向OS申请一大块空间
需要注意的点:
- 用户向内存池申请一块大小为n的空间时,为了效率,内存池会切割出多块大小为n的空间,给用户一个,其它剩余的会连接到哈希桶对应下标处。避免下一次申请
- 可能用户申请的空间会不是8的整数倍,内存池切割的内存块大小会向上对齐到8的整数倍。比如:申请5字节空间,内存池会切割8字节的空间
- 一个进程中有一个空间配置器,进程中所有容器需要的空间都到对应空间配置器申请。进程终止,对应空间配置器空间释放
1.外碎片
:由于频繁申请小块内存,导致整块内存中,被申请的内存块不连续,碎片化了,如果下一次需要申请一大块内存,内存空间够,但是由于不连续,导致申请不出来
内核针对大量申请在堆上小块内存导致碎片化的问题,是用来slab分配器来解决。结构类似二级空间配置器的哈希结构
内核已经有了slab分配器来解决小块内存的申请,为什么STL中还有设计一个?
- 因为首先内核是针对所有程序的,并不是只指针对STL。其次STL配置器主要是为了解决效率问题,不需要频繁项OS申请空间,只是顺便解决了内存碎片问题
2.内碎片:给的内存数比实际要的内存数多,导致空间浪费,二级配置器切割内存块向上对齐8的整数倍,就造成了内碎片问题