STL——空间配置器
Posted 两片空白
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了STL——空间配置器相关的知识,希望对你有一定的参考价值。
目录
前言
我们知道STL容器在不断保存数据时,当保存的的数据个数超过容器容量时,需要进行扩容。但是,当不断保存数据时,就可能需要不断的进行扩容。此时,扩容需要不断的向操作系统申请空间,释放空间。操作系统是很繁忙的,这样会大大影响操作系统的效率。
于是就出现了空间配置器。
一.概念
空间配置器:是操作系统开辟的一大段内存空间。STL需要扩容申请内存时,就从空间配置器中申请,不需要再经过操作系统。并且,它还能回收释放的空间,供下一次使用。
二.优点
- 提高效率
STL容器申请空间不需要频繁向操作系统申请,而是需要向空间适配器申请,只是当空间适配器空间不够时,才会向操作系统申请。
空间适配器还可以回收STL容器释放的空间,供下一次使用。
- 避免内存碎片
后面有解释。
- 更好的管理内存
三.空间配置器原理
空间配置器有两级结构,一级空间配置器是用来处理大块内存,二级空间配置器处理小块内存。SGI-STL规定以128字节作为小块内存和大块内存的分界线。
为什么这样区分成两级?
因为STL容器,一般申请的都会是小块的内存。
二级空间配置器,主要是管理容器申请空间和释放的空间。
如果用户申请的空间直接大于的128字节直接找的是一级空间配置器申请空间。
3.1 一级空间配置器
一级空间配置器原理很简单,直接是对malloc和free进行了封装,并且增加了C++中的set_new_handle思想,即申请空间失败抛异常机制。
主要的作用是:向操作系统申请内存,申请失败会抛异常。
为什么不直接用C++的new和delete,因为这里并不需要调用构造函数和析构函数。
用户可以自定义申请空间失败的措施,如果没有设置,就会抛异常。
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 /* n */)
{
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;
3.2 二级空间配置器
二级空间配置器专门负责处理小于128字节的小块内存。
SGI-STL采用了内存池的技术来提高申请空间的速度以及减少额外空间的浪费,采用哈希桶的方式来提高用户获取空间的速度和高效管理。
3.2.1 内存池技术
内存池就是,先申请一块较大的内存块做为备用,当需要内存时,直接从内存池中取内存,当内存池中内存不够时,使用一级空间配置器,向内存中申请大块空间。当用户不用申请的空间时,直接归还内存池。
这样就避免了频繁向系统申请小块内存找出的效率低,内存碎片的问题。
但是这样会有一个问题:如何归还?
用户申请空间很简单,直接从内存池中申请。但是,当用户释放该空间时,并不知道这块空间应该放在内存池的什么位置。归还成了一个问题。
在STL配置器中,使用哈希桶的技术来解决这一问题。
3.2.2 哈希桶技术
说明:用户申请的空间基本上都会是4的整数倍,其它空间大小几乎很少用到。所以哈希桶并不需要对1~128字节的空间进行管理,也就是并不需要128个桶。SGI-STL将用户申请的内存块向上对齐到了8字节的整数倍。
为什么不用链表来管理归还的空间?
因为用户申请空间,查找合适的内存块时,效率低。
为什么向上对齐到了8字节的整数倍?
因为在哈希桶下,是以链表的形式将所有内存块连接起来的。该内存块至少需要保存下一个内存块的地址,在64位系统下,为8字节。
但是这样造成了内存碎片问题。
用户归还的空间,会被连接到哈希桶对应空间大小的位置下。
结构:
大致流程:
容器进行扩容,如果申请的空间是大于128字节,直接向一级空间配置器申请。如果小于128字节,先查找哈希桶对应大小位置是否为空,不为空,直接从该位置申请空间,如果该位置为空,向内存池申请。当内存池空间不够了会直接向OS申请一大块空间。
注意点:
用户向内存池申请一块大小为n的空间时,为了效率,内存池会切割出多块大小为n的空间,给用户一个,其它剩余的会连接到哈希桶对应下标处。避免下一次申请。
可能用户申请的空间会不是8的整数倍,内存池切割的内存块大小会向上对齐到8的整数倍。比如:申请5字节空间,内存池会切割8字节的空间。
一个进程中有一个空间配置器,进程中所有容器需要的空间都到对应空间配置器申请。进程终止,对应空间配置器空间释放。
四.内存碎片问题
4.1 外碎片
由于频繁申请小块内存,导致整块内存中,被申请的内存块不连续,碎片化了,如果下一次需要申请一大块内存,内存空间够,但是由于不连续,导致申请不出来。
内核针对大量申请在堆上小块内存导致碎片化的问题,是用来slab分配器来解决。结构类似二级空间配置器的哈希结构。
内核已经有了slab分配器来解决小块内存的申请,为什么STL中还有设计一个?
因为首先内核是针对所有程序的,并不是只指针对STL。其次STL配置器主要是为了解决效率问题,不需要频繁项OS申请空间,只是顺便解决了内存碎片问题。
4.2 内碎片
内碎片问题:给的内存数比实际要的内存数多,导致空间浪费。
二级配置器切割内存块向上对齐8的整数倍,就造成了内碎片问题。
五.具体细节
5.1 申请空间
5.2 填充内存块
5.3 向内存池中索要空间
以上是关于STL——空间配置器的主要内容,如果未能解决你的问题,请参考以下文章