考虑到一些容器,如vector、list,会存在一些问题:
(1)效率低。动态在堆上开辟一块空间,需要在堆上找合适的内存块。
(2)存在内存碎片。导致需要一块较大的空间时,需要在堆上找合适的内存块找不到。
(3)开销太大。为了管理malloc的空闲空间,每一个独立块的最前面都包含了一个“头部信息”,属于额外开销。
(4)存在内存泄露。
(5)代码复用率低。每个容器都会使用相同的new一块空间。
(6)没有空间不足的应对措施。
针对以上问题,提出了空间配置器,可以帮助我们来管理空间,将容器中出现的问题考虑进去。
空间配置器
空间配置器,就是用来配置、管理和释放空间的,给所有的容器包括算法提供生存空间。
作用:
(1)提高代码复用率,功能模块化。
(2)减少内存碎片问题。
(3)提高内存分配的效率。
(4)有内存不足时的应对措施。
(5)隐藏实际中对存储空间的分配及释放细节,确保所有被分配的存储空间都最终获得释放。
(5)考虑多线程状态。
考虑到小型区块可能导致的内存碎片问题,设置了两级空间配置器。分别为:一级空间配置器、二级空间配置器。当区块大于128字节,调用一级空间配置器;小于等于128字节,为了降低额外开销,用底层较复杂的二级空间配置器。
一级空间配置器
用malloc()、free()、realloc()等C函数执行内存配置、释放、重配置操作,并实现出类似的C++new_hanle的机制
//一级空间配置器
//> 128
typedef void (*PMallocHandler)();//是一个类型,函数指针
template <int inst>
class MallocAllocTemplate{
public:
static void* Allocate(size_t n)
{
void *result = malloc(n);
if (NULL == result)
result = OOM_Malloc(n);//申请空间失败的应急措施
__TRACE_DEBUG("一级:%d\n", n);
return result;
}
static void Deallocate(void* p, size_t)
{
__TRACE_DEBUG("一级释放\n");
free(p);
}
static void* Reallocate(void* p,size_t,size_t newSize)
{
void *result = realloc(p, newSize);
if (0 == result)
result = OOM_Realloc(p, newSize);
return result;
}
private:
static void *OOM_Malloc(size_t n)
{
PMallocHandler mallocHandle;
void* res;
__TRACE_DEBUG("一级:第一次申请失败,OOM处理: %d\n", n);
for (;;)
{
mallocHandle = _mallocHandle;
if (NULL == mallocHandle)
throw std::bad_alloc();
//尝试去释放已经获取但不用的堆空间
mallocHandle();
res = malloc(n);
if (res)//申请成功
return res;
}
}
static void *OOM_Realloc(void* p, size_t ,size_t newSize)
{
PMallocHandler mallocHandle;
void* res;
for (;;)
{
mallocHandle = _mallocHandle;
if (NULL == mallocHandle)
throw std::bad_alloc();
//尝试去释放已经获取但不用的堆空间
mallocHandle();
res = realloc(p, newSize);
if (res)//申请成功
return res;
}
}
//释放方法
PMallocHandler SetMallocHandle(PMallocHandler mallocHandle)
{
PMallocHandler old = _mallocHandle;
_mallocHandle = mallocHandle;
return old;
}
private:
static PMallocHandler _mallocHandle;
};
PMallocHandler MallocAllocTemplate<0>::_mallocHandle = NULL;
二级空间配置器
SGI二级空间配置器的原理是:当区块小于128字节,则以内存池(memory pool)管理,回收时管理一个用户归还的空间,类似于哈希桶。每次配置一块内存,并维护对应的自由链表(free_list)。为了方便管理,SGI二级配置器会对齐到8个字节。(例:需要30字节的空间,自动调整到32字节)。维护16个free_lists,各自管理大小分别为
8,16,24,32,40,48,56,64,72,80,88,96,104,112,120,128字节。
内存池开始位置:start_free
内存池结束位置:end_free
所需空间的大小:n
内存池大小:heap_size=end_free-start_free.
20个所需的空间大小:total_byte=n*20.
所需空间大小为n,计算索引index=(n+7)/8-1。
查看用户管理的空间中(哈希桶)是否有内存块。有,头删(或者尾删)。如果没有,refill空间。
//计算索引
static size_t FREELIST_INDEX(size_t bytes) {
return (((bytes)+__ALIGN - 1) / __ALIGN - 1);
//对齐到当前8的整数倍
static size_t ROUND_UP(size_t bytes) {
return (((bytes)+__ALIGN - 1) & ~(__ALIGN - 1));
}
refill分几种情况:
1.内存池空间充足(内存池剩余空间>20个所需的空间块),一次性提供。
2.内存池剩余空间至少能够提供一个(1个所需的空间块=<内存池剩余空间<20)。将这一个空间返回给用户。
3.内存池连一个也提供不了(内存池剩余空间<1个所需的空间块),向系统要空间。
step1:计算向系统要的空间大小:2*total_bytes+[heap_size/16]向上8字节对齐
step2:处理剩余空间。将内存池剩下的空间挂在用户管理的空间上。
step3:向系统所索要内存空间。当系统空间充足,malloc成功;当系统空间不足,就会申请失败,会首先在用户管理的内存空间中向后找。如果找到,补充到内存池中,如果找不到,就向一级空间配置器中要空间。
//向桶下边填充内存块
static void* ReFill(size_t n)
{
int nobjs = 20;
//向内存池中要内存块
char* chunk = (char*)ChunkAlloc(n, nobjs);
//内存池中只能够一个内存块
if (1 == nobjs)
return chunk;
size_t index = FREELIST_INDEX(n);
OBJ* cur = (OBJ*)(chunk + n);//指向下一个内存块
__TRACE_DEBUG("二级:内存池补充%d个小块内存\n", nobjs);
//将剩余的内存块挂在链表上
while (--nobjs)
{
//头插法(系统中给的是尾插)
cur->_freelistlink = _freeList[index];
_freeList[index] = cur;
cur = (OBJ*)((char*)cur + n);//向后走一个结点
}
return chunk;
}
整体代码:
#pragma once
#include <iostream>
using namespace std;
#include <string.h>
#include <malloc.h>
#include <stdarg.h>
using namespace std;
#define _DEBUG_
static string GetFileName(const string& path)
{
char ch = '/';
#ifdef _WIN32
ch = '\\';
#endif
size_t pos = path.rfind(ch);
if (pos == string::npos)
return path;
else
return path.substr(pos + 1);
}
// 用于调试追踪的trace log
inline static void _trace_debug(const char * funcName,
const char * fileName, int line, char* format, ...)
{
#ifdef _DEBUG_
fprintf(stdout, "[%s:%d]%s", GetFileName(fileName).c_str(), line, funcName);
// 输出用户信息
va_list args;
va_start(args, format);
vfprintf(stdout, format, args);
va_end(args);
#endif
}
#define __TRACE_DEBUG(...) \
_trace_debug(__FUNCDNAME__, __FILE__, __LINE__, __VA_ARGS__);
//一级空间配置器
//> 128
typedef void (*PMallocHandler)();//是一个类型,函数指针
template <int inst>
class MallocAllocTemplate{
public:
static void* Allocate(size_t n)
{
void *result = malloc(n);
if (NULL == result)
result = OOM_Malloc(n);//申请空间失败的应急措施
__TRACE_DEBUG("一级:%d\n", n);
return result;
}
static void Deallocate(void* p, size_t)
{
__TRACE_DEBUG("一级释放\n");
free(p);
}
static void* Reallocate(void* p,size_t,size_t newSize)
{
void *result = realloc(p, newSize);
if (0 == result)
result = OOM_Realloc(p, newSize);
return result;
}
private:
static void *OOM_Malloc(size_t n)
{
PMallocHandler mallocHandle;
void* res;
__TRACE_DEBUG("一级:第一次申请失败,OOM处理: %d\n", n);
for (;;)
{
mallocHandle = _mallocHandle;
if (NULL == mallocHandle)
throw std::bad_alloc();
//尝试去释放已经获取但不用的堆空间
mallocHandle();
res = malloc(n);
if (res)//申请成功
return res;
}
}
static void *OOM_Realloc(void* p, size_t ,size_t newSize)
{
PMallocHandler mallocHandle;
void* res;
for (;;)
{
mallocHandle = _mallocHandle;
if (NULL == mallocHandle)
throw std::bad_alloc();
//尝试去释放已经获取但不用的堆空间
mallocHandle();
res = realloc(p, newSize);
if (res)//申请成功
return res;
}
}
//释放方法
PMallocHandler SetMallocHandle(PMallocHandler mallocHandle)
{
PMallocHandler old = _mallocHandle;
_mallocHandle = mallocHandle;
return old;
}
private:
static PMallocHandler _mallocHandle;
};
PMallocHandler MallocAllocTemplate<0>::_mallocHandle = NULL;
//二级空间配置器
//<= 128
template<int inst>
class DefaultAllocateTemplate
{
public:
static void* Allocate(size_t n)
{
if (n > __MAX_BYTES)//大于128字节,使用一级空间配置器分配空间
return MallocAllocTemplate<0>::Allocate(n);
__TRACE_DEBUG("二级: %d\n", n);
size_t index = FREELIST_INDEX(n);
if (NULL == _freeList[index])//当前的桶下已经没有空间
{
__TRACE_DEBUG("二级: %d号桶中(需要空间:%d)没有可以用的空间,需要内存池补充\n",index, n);
return ReFill(ROUND_UP(n));//给桶下填充空间
}
//当前桶下有空间,则会申请空间成功
void* res = _freeList[index];//第一个内存块的位置
_freeList[index] = _freeList[index]->_freelistlink;//头删
return res;//将第一个内存块返给用户
}
//向桶下边填充内存块
static void* ReFill(size_t n)
{
int nobjs = 20;
//向内存池中要内存块
char* chunk = (char*)ChunkAlloc(n, nobjs);
//内存池中只能够一个内存块
if (1 == nobjs)
return chunk;
size_t index = FREELIST_INDEX(n);
OBJ* cur = (OBJ*)(chunk + n);//指向下一个内存块
__TRACE_DEBUG("二级:内存池补充%d个小块内存\n", nobjs);
//将剩余的内存块挂在链表上
while (--nobjs)
{
//头插法(系统中给的是尾插)
cur->_freelistlink = _freeList[index];
_freeList[index] = cur;
cur = (OBJ*)((char*)cur + n);//向后走一个结点
}
return chunk;
}
static void* ChunkAlloc(size_t size, int& nobjs)
{
size_t totalBytes = size*nobjs;//需要的空间的大小
size_t leftBytes = _endFree - _startFree;//剩余的空间大小
if (leftBytes >= totalBytes)//内存池剩余的空间足够提供(>=20)
{
__TRACE_DEBUG("二级:内存池可以提供20个%d个小块内存\n", size);
char* res = _startFree;
_startFree += totalBytes;
return res;//返回给用户使用
}
else if (leftBytes >= size)//至少可以提供一个(1~20)
{
__TRACE_DEBUG("二级:内存池可以提供%d个%d个小块内存\n", nobjs, size);
nobjs = leftBytes / size;
char* res = _startFree;
_startFree += nobjs*size;
return res;//将剩余的空间返回给用户使用
}
else//一个都不够,向系统要(<1)
{
__TRACE_DEBUG("二级:内存池连一个%d字节的小块内存也不能提供\n",size);
size_t getBytes = totalBytes * 2 + ROUND_UP(_heapSize>>4);//向系统所要空间
size_t index = FREELIST_INDEX(leftBytes);
if (leftBytes > 0)//将剩余的空间插入到链表中
{
((OBJ*)_startFree)->_freelistlink = _freeList[index];
_freeList[index] = (OBJ*)_startFree;
}
index = 0;
_startFree = (char*)malloc(getBytes);
__TRACE_DEBUG("二级:向内存池中补充空间%d\n", getBytes);
if (NULL == _startFree)
{
//在二级空间配置器中找更大的内存块
for (index=ROUND_UP(size); index < __MAX_BYTES / __ALIGN; ++index)
{
if (_freeList[index])
{
__TRACE_DEBUG("二级:向系统堆索要空间失败,找更大的内存块%d字节\n", (index + 1)*__ALIGN);
_startFree = (char*)_freeList[index];
_freeList[index] = _freeList[index]->_freelistlink;
_endFree = _startFree + (index + 1)*__ALIGN;
return ChunkAlloc(size, nobjs);
}
}
_endFree = 0;//防止抛异常
__TRACE_DEBUG("二级:山穷水尽了,向一级空间配置器索要%d的空间\n", getBytes);
//向一级空间配置器要空间
_startFree = (char*)MallocAllocTemplate<0>::Allocate(getBytes);
}
//更新
_endFree = _startFree + getBytes;
_heapSize += getBytes;
return ChunkAlloc(size, nobjs);
}
}
//释放
static void DeAllocate(void* p, size_t n)
{
if (n > __MAX_BYTES){
MallocAllocTemplate<inst>::Deallocate(p, n);
return;
}
//二级空间配置器,释放时将其放入的管理用户空间中
else
{
__TRACE_DEBUG("二级:释放%d\n", n);
size_t index = FREELIST_INDEX(n);
((OBJ*)p)->_freelistlink = _freeList[index];
_freeList[index] = (OBJ*)p;
}
}
private:
enum { __ALIGN = 8 };
enum { __MAX_BYTES = 128 };
enum { __NFREELISTS = __MAX_BYTES / __ALIGN };//哈希桶的大小
private:
//对齐到当前8的整数倍
static size_t ROUND_UP(size_t bytes) {
return (((bytes)+__ALIGN - 1) & ~(__ALIGN - 1));
}
union OBJ{
union OBJ* _freelistlink;//链表中的下一个结点
char clientdata[1];//当前结点的值
};
//计算索引
static size_t FREELIST_INDEX(size_t bytes) {
return (((bytes)+__ALIGN - 1) / __ALIGN - 1);
}
private:
static char* _startFree;
static char* _endFree;
static size_t _heapSize;
static OBJ* _freeList[__NFREELISTS];
};
//静态成员在类外初始化
template<int inst>
char* DefaultAllocateTemplate<inst>::_startFree = NULL;
template<int inst>
char* DefaultAllocateTemplate<inst>::_endFree = NULL;
template<int inst>
size_t DefaultAllocateTemplate<inst>::_heapSize = 0;
template<int inst>
typename DefaultAllocateTemplate<inst>::OBJ* \
DefaultAllocateTemplate<inst>::_freeList[\
DefaultAllocateTemplate<inst>::__NFREELISTS]\
= { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, };
#ifdef USE_MALLOC
typedef MallocAllocTemplate<0> _Alloc;
#else
typedef DefaultAllocateTemplate<0> _Alloc;
#endif
template<class T,class Alloc>
class SimpleAlloc
{
public:
static void* Allocate(size_t n)
{
return (0 == n) ? 0 : Alloc::Allocate(sizeof(T)*n);
}
static void* Allocate(void)
{
return (0 == n) ? 0 : Alloc::Allocate(sizeof(T));
}
static void DeAllocate(void* p, size_t n)
{
Alloc::DeAllocate(p, n*sizeof(T));
}
static void DeAllocate(void* p)
{
Alloc::DeAllocate(p, sizeof(T));
}
};
void testAllocate()
{
//一级
int* p1 = (int*)SimpleAlloc<int, _Alloc>::Allocate(100);//100*4>128
SimpleAlloc<int, _Alloc>::DeAllocate(p1, 100);
//二级
int* p2 = (int*)SimpleAlloc<int, _Alloc>::Allocate(2);
SimpleAlloc<int, _Alloc>::DeAllocate(p2, 2);
int* p3 = (int*)SimpleAlloc<int, _Alloc>::Allocate(5);
int* p4 = (int*)SimpleAlloc<int, _Alloc>::Allocate(3);
SimpleAlloc<int, _Alloc>::DeAllocate(p3, 5);
SimpleAlloc<int, _Alloc>::DeAllocate(p4, 3);
}
输出结果:
[mallocalloc.h:51]?Allocate@?$MallocAllocTemplate@$0A@@@SAPAXI@Z一级:400
[mallocalloc.h:57]?Deallocate@?$MallocAllocTemplate@$0A@@@SAXPAXI@Z一级释放
[mallocalloc.h:130]?Allocate@?$DefaultAllocateTemplate@$0A@@@SAPAXI@Z二级: 8
[mallocalloc.h:135]?Allocate@?$DefaultAllocateTemplate@$0A@@@SAPAXI@Z二级: 0号桶中(需要空间:8)没有可以用的空间,需要内存池补充
[mallocalloc.h:194]?ChunkAlloc@?$DefaultAllocateTemplate@$0A@@@SAPAXIAAH@Z二级:内存池连一个8字节的小块内存也不能提供
[mallocalloc.h:206]?ChunkAlloc@?$DefaultAllocateTemplate@$0A@@@SAPAXIAAH@Z二级:向内存池中补充空间320
[mallocalloc.h:179]?ChunkAlloc@?$DefaultAllocateTemplate@$0A@@@SAPAXIAAH@Z二级:内存池可以提供20个8个小块内存
[mallocalloc.h:158]?ReFill@?$DefaultAllocateTemplate@$0A@@@SAPAXI@Z二级:内存池补充20个小块内存
[mallocalloc.h:249]?DeAllocate@?$DefaultAllocateTemplate@$0A@@@SAXPAXI@Z二级:释放8
[mallocalloc.h:130]?Allocate@?$DefaultAllocateTemplate@$0A@@@SAPAXI@Z二级: 20
[mallocalloc.h:135]?Allocate@?$DefaultAllocateTemplate@$0A@@@SAPAXI@Z二级: 2号桶中(需要空间:20)没有可以用的空间,需 要内存池补充
[mallocalloc.h:186]?ChunkAlloc@?$DefaultAllocateTemplate@$0A@@@SAPAXIAAH@Z二级:内存池可以提供20个24个小块内存
[mallocalloc.h:158]?ReFill@?$DefaultAllocateTemplate@$0A@@@SAPAXI@Z二级:内存池补充6个小块内存
[mallocalloc.h:130]?Allocate@?$DefaultAllocateTemplate@$0A@@@SAPAXI@Z二级: 12
[mallocalloc.h:135]?Allocate@?$DefaultAllocateTemplate@$0A@@@SAPAXI@Z二级: 1号桶中(需要空间:12)没有可以用的空间,需 要内存池补充
[mallocalloc.h:186]?ChunkAlloc@?$DefaultAllocateTemplate@$0A@@@SAPAXIAAH@Z二级:内存池可以提供20个16个小块内存
[mallocalloc.h:249]?DeAllocate@?$DefaultAllocateTemplate@$0A@@@SAXPAXI@Z二级:释放20
[mallocalloc.h:249]?DeAllocate@?$DefaultAllocateTemplate@$0A@@@SAXPAXI@Z二级:释放12
空间配置器也存在一些缺点,但总的来说是优点大于缺点的。
(1)内存并没有还给系统,只换给了空间配置器。有可能在申请大的内存块就不够了。
(2)外部碎片的问题解决了,又引入了内部碎片,自由链表下挂的小结点过多导致。
(3)只有在程序真正退出的时候,内存才释放。