一:为什么需要空间适配器?
我们知道地址空间的结构如下:
假设系统分配16byte,8byte,16byte,4byte,我们仅仅只是使用了16个字节的空间,当我们下次载要开一个32个字节的大小的空间,虽然内存够却开不了空间,因为空间不连续,只好在内存从新分配一个更大的空间,
1:出现小内存块的问题,
2:频繁的分配小块内存,效率比较低
二:我们看下STL中关于空间配置器:
一级空间配置器:
主要有三部分组成:
1:allocate调用malloc()开辟空间
2:deallcate调用free()释放空间;
3:模拟c++中的set_new_handler解决内存不足的问题;
一级空间配置器的实现
typedef void (*HANDLER_FUNC) ();//定义函数指针
template<int inst>
//表明开一个很大的内存块
class _MallocAllocTemplate
{
private:
static HANDLER_FUNC __malloc_alloc_oom_handler;
public:
static void*OOM_Malloc(size_t n) //主要解决内存不足的问题
{
void *result = NULL;
while (1)
{
if (0 == __malloc_alloc_oom_handler)
{
throw bad_alloc();
}
__malloc_alloc_oom_handler();
result = malloc(n);
if (result)
break;
}
return result;
}
//开空间
static void * Allocate(size_t n)
{
void *result = malloc(n);//开空间
__TRACE_DEBUG("一级空间配置器申请:0x%p,%d\n", result, n);
if (0 == result)//表明开空间失败
result = OOM_Malloc(n);
return result;
}
//释放空间
static void Deallocate(void *p, size_t n)
{
__TRACE_DEBUG("一级空间配置器释放:0x%p,%d\n",p, n);
free(p);
}
HANDLER_FUNC SetMallocHandler(HANDLER_FUNC f)
{
HANDLER_FUNC old=f;
__malloc_alloc_oom_handler = f;
return old;
}
};
//静态变量需要在类外面进行初始化
template<int inst>
HANDLER_FUNC _MallocAllocTemplate<inst>::__malloc_alloc_oom_handler = 0;
如果频繁的进行小块内存的申请和释放,我们如果解决呢?这就需要二级空间适配器.
三:二级空间适配器:
所谓的二级空间适配器就是由内存池和自由链表(free_list)组成.
我们用两个指针start_free表明内存池起始位置,end_free表明内存池尾部位置,
所谓的自由链表实际上是由16个大小的指针数组下面可以挂节点,我们可以看成直接定址的哈希,然后在表的下面挂上节点
二级空间配置器的做法是:
1:当内存空间大于128个字节时我们就认为是大空间,直接调用一级空间配置器中malloc分配内存
2:当内存块小于128时,由内存池管理,具体做法是每次配置一块内存要维护相应的自由链表,下次再进行相同的内存需求就直接在自由链表上拔出,如果用完需要归还,直接把自由链表的内存归还给内存池;
我们来看具体是怎么实现的?
free_list节点结构:
union Obj//free_list节点的结构
{
union Obj*FreeListLink;//自由链表下的内存块
char client_data[1];
};
空间配置Allocate()函数:
当需要的字节数大于128字节直接调用一级空间配置器,
如果小于128字节我们认为是小块内存,就可以采用内存池的管理机制,交给自由链表处理;当自由链表下面有内存块时,我们把从内存池取的内存连到自由链表的内存块下面,实际就是节点的插入:如果自由链表没有可用的区块那就将区块上调至8的倍数,然后调用rifill()为free_list填充空间
static void *Allocate(size_t n)
{
void *ret = NULL;
//大于128个字节调用一级空间配置器
if (n >__MAX_BYTES)
{
//调用一级空间配置器
return _MallocAllocTemplate<inst>::Allocate(n);
}
__TRACE_DEBUG("二级空间配置器申请字节:%d\n", n);
size_t index = FREELIST_INDEX(n);
if (FreeList[index])//自由链表有内存块
{
Obj *ret= FreeList[index];
FreeList[index] = ret->FreeListLink;
return ret;
}
else
{
//调用refill从内存池填充自由链表,并返回内存池的第一个内存块
size_t bytes = RoundUp(n);
return Refill(bytes);
}
return ret;
}
空间适配函数Dellocate()
1:内存块的大小大于128个字节,调一级空间配置器的dellocate()来进行空间的释放
2:如果是小块内存直接把小块内存回收到自由链表中,这样就可以避免系统频繁的申请和释放内存造成的内存碎片的问题;
static void Dellocate(void *p, size_t n)
{
if (p == NULL)
{
return ;
}
//表明大于128字节就调用一级空间配置器
if (n > __MAX_BYTES)
{
return _MallocAllocTemplate<inst>::Deallocate(p, n);
}
else
{
//放回free_list中
size_t index = FREELIST_INDEX(n);
((Obj*)p)->FreeListLink = FreeList[index];
FreeList[index] = (Obj*)p;
}
__TRACE_DEBUG("二级空间配置器释放:%d\n", n);
}
};
重新填充Refill()
前面我们知道在alloc()开辟空间时,可能自由链表上的没有内存块,调用refill(),为free_list重新填充空间,新的内存块来自内存池,缺省取得20个新节点,但是内存池的空间可能小于20;
1;当内存块为1时,直接把这个内存块给调用者,就没有必要再链上自由链表;
2:否则就将内存池上的内存块拿下链到自由链表上
//表明从内存池上拿内存块填充自由链表
static void *Refill(size_t size)
{
size_t nobjs = 20;//申请取20个内存块
char *chunk = ChunkAlloc(size, nobjs);//表明将内存块作为free_list的新节点
//如果只获得一个内存块,就将这个内存块直接个调用者用,free_list没有新节点
if (nobjs == 1)
{
return chunk;
}
//调整free_list.将新节点链上自由链表中
size_t index = FREELIST_INDEX(size);
for (size_t i = 1; i < nobjs; ++i)
{
Obj*obj = (Obj*)(chunk + i*size);
obj->FreeListLink = FreeList[index];
FreeList[index] = obj;
}
return chunk;
}
ChunkAlloc()函数:
那么问题的关键来了,既然是在内存池里面取内存,取多少?万一内存池里面也没有内存如果解决呢?这里面的工作全都是由ChunkAlloc()来完成,让我们来看看它是如何工作的?
ChunkAlloc()函数首先判断内存池的剩余量(_EndFree_StartFree),当剩余的内存充足,表明可以满足需求,直接调出20个内存块的大小内存给自由链表;如果剩余量不足以提供20个内存块,但还足够供应1个以上的内存块,这时我们将nobjs修改为实际供应的内存块数,如果连一个内存块的空间也无法提供我们把内存池上的剩余空间给自由链表,如个内存池上也没有内存,我们在heap上malloc出空间填充内存池,如果malloc失败我们向自由链表上找剩余的内存.如果自由链表上也没有内存,就需要调用一级空间配置器.
static char*ChunkAlloc(size_t size, size_t &nobjs)
{
size_t TotalBytes = size*nobjs;//表明总共需要需要的内存的大小
size_t BytesLeft = _EndFree - _StartFree;//表明内存池中剩余的内存的多少
//表明剩余的内存完全满足需求
if (BytesLeft >= TotalBytes)
{
char *ret = _StartFree;
_StartFree += TotalBytes;
return ret;
}
//表明内存池的大小不能满足需求量但是可能满足一个块的大小
else if (BytesLeft >= size)
{
nobjs = BytesLeft / size;
TotalBytes = size*nobjs;
char *ret = _StartFree;
_StartFree += TotalBytes;
return ret;
}
else
{
//表明内存池的大小连一个块的大小都提供不了;
size_t BytesToGet = 2 * TotalBytes + RoundUp(_HeapSize >> 4);
if (BytesLeft > 0)
{
size_t index = FREELIST_INDEX(BytesLeft);
//表明将内存池的剩余的内存配给自由链表上
((Obj*)_StartFree)->FreeListLink = FreeList[index];
FreeList[index] = (Obj*)_StartFree;
}
//配置heap空间,用来补充内存池
_StartFree = (char*)malloc(BytesToGet);
//表明malloc空间失败
if (_StartFree == 0)
{
//继续向后搜索free_list中剩余的内存块
for (size_t i = size; i < __NFREELISTS; ++i)
{
//free_list中还有没有被用的内存块
if (FreeList[i])
{
//释放free_list中的内存块(把内存块归还给内存池)
Obj*cur = FreeList[i];
FreeList[i] = cur->FreeListLink;
_StartFree = (char *)cur;
_EndFree = _StartFree + (i + 1)*__ALIGN;
return ChunkAlloc(size, nobjs);
}
}
//表明free_list中也没有剩余的内存块
_EndFree = 0;
//此时需要调用一级空间配置器
_StartFree = (char *)_MallocAllocTemplate<inst>::Allocate(BytesToGet);
}
_HeapSize += BytesToGet;//从系统分配的总字节数(便于下次调用时使用)
_EndFree = _StartFree + BytesToGet;
return ChunkAlloc(size, nobjs);//递归调用,为了修正nobjs
}
}
二级空间配置器的实现:
二级空间适配器的实现:
template<bool threads,int inst>
class _DefaultAllocTemplate
{
enum{ __ALIGN = 8 };//小区快上调的边界
enum{__MAX_BYTES = 128 };//小区快的大小
enum{ __NFREELISTS = __MAX_BYTES / __ALIGN };//free_list个数
union Obj//free_list节点的结构
{
union Obj*FreeListLink;//自由链表下的内存块
char client_data[1];
};
static Obj*FreeList[__NFREELISTS];//自由链表节点的下标从0开始
//chunk alloction state//内存块的状态
static char *_StartFree;//表明内存池的起始位置
static char *_EndFree;//内存池结束的位置
static size_t _HeapSize;//反馈提哦啊接向系统申请字节大小
static size_t FREELIST_INDEX(size_t bytes)
{
return (((bytes)+__ALIGN - 1) / __ALIGN - 1);//对齐到8
}
//将bytes上调到8的倍数
static size_t RoundUp(size_t bytes)
{
return (((bytes)+__ALIGN - 1)&~(__ALIGN - 1));
}
//表明从内存池中取空间给free_list
static char*ChunkAlloc(size_t size, size_t &nobjs)
{
size_t TotalBytes = size*nobjs;//表明总共需要需要的内存的大小
size_t BytesLeft = _EndFree - _StartFree;//表明内存池中剩余的内存的多少
//表明剩余的内存完全满足需求
if (BytesLeft >= TotalBytes)
{
char *ret = _StartFree;
_StartFree += TotalBytes;
return ret;
}
//表明内存池的大小不能满足需求量但是可能满足一个块的大小
else if (BytesLeft >= size)
{
nobjs = BytesLeft / size;
TotalBytes = size*nobjs;
char *ret = _StartFree;
_StartFree += TotalBytes;
return ret;
}
else
{
//表明内存池的大小连一个块的大小都提供不了;
size_t BytesToGet = 2 * TotalBytes + RoundUp(_HeapSize >> 4);
if (BytesLeft > 0)
{
size_t index = FREELIST_INDEX(BytesLeft);
//表明将内存池的剩余的内存配给自由链表上
((Obj*)_StartFree)->FreeListLink = FreeList[index];
FreeList[index] = (Obj*)_StartFree;
}
//配置heap空间,用来补充内存池
_StartFree = (char*)malloc(BytesToGet);
//表明malloc空间失败
if (_StartFree == 0)
{
//继续向后搜索free_list中剩余的内存块
for (size_t i = size; i < __NFREELISTS; ++i)
{
//free_list中还有没有被用的内存块
if (FreeList[i])
{
//释放free_list中的内存块(把内存块归还给内存池)
Obj*cur = FreeList[i];
FreeList[i] = cur->FreeListLink;
_StartFree = (char *)cur;
_EndFree = _StartFree + (i + 1)*__ALIGN;
return ChunkAlloc(size, nobjs);
}
}
//表明free_list中也没有剩余的内存块
_EndFree = 0; //(如果_EndFree不为0,下一次从内存池中取,就算抛异常以为会有内存,分配使用时程序崩溃)
//此时需要调用一级空间配置器
_StartFree = (char *)_MallocAllocTemplate<inst>::Allocate(BytesToGet);
}
_HeapSize += BytesToGet;//从系统分配的总字节数(便于下次调用时使用)
_EndFree = _StartFree + BytesToGet;
return ChunkAlloc(size, nobjs);//递归调用,为了修正nobjs
}
}
//表明从内存池上拿内存块填充自由链表
static void *Refill(size_t size)
{
size_t nobjs = 20;//申请取20个内存块
char *chunk = ChunkAlloc(size, nobjs);//表明将内存块作为free_list的新节点
//如果只获得一个内存块,就将这个内存块直接个调用者用,free_list没有新节点
if (nobjs == 1)
{
return chunk;
}
//调整free_list.将新节点链上自由链表中
size_t index = FREELIST_INDEX(size);
for (size_t i = 1; i < nobjs; ++i)
{
Obj*obj = (Obj*)(chunk + i*size);
obj->FreeListLink = FreeList[index];
FreeList[index] = obj;
}
return chunk;
}
public:
//二级空间配置器的内存的申请
//超过128字节表明大块内存需要到内存池里面申请空间
static void *Allocate(size_t n)
{
void *ret = NULL;
//大于128个字节调用一级空间配置器
if (n >__MAX_BYTES)
{
//调用一级空间配置器
return _MallocAllocTemplate<inst>::Allocate(n);
}
__TRACE_DEBUG("二级空间配置器申请字节:%d\n", n);
size_t index = FREELIST_INDEX(n);
if (FreeList[index])//自由链表有内存块
{
Obj *ret= FreeList[index];
FreeList[index] = ret->FreeListLink;
return ret;
}
else
{
//调用refill从内存池填充自由链表,并返回内存池的第一个内存块
size_t bytes = RoundUp(n);
return Refill(bytes);
}
return ret;
}
//空间释放
static void Dellocate(void *p, size_t n)
{
if (p == NULL)
{
return ;
}
//表明大于128字节就调用一级空间配置器
if (n > __MAX_BYTES)
{
return _MallocAllocTemplate<inst>::Deallocate(p, n);
}
else
{
//放回free_list中
size_t index = FREELIST_INDEX(n);
((Obj*)p)->FreeListLink = FreeList[index];
FreeList[index] = (Obj*)p;
}
__TRACE_DEBUG("二级空间配置器释放:%d\n", n);
}
};
//静态成员变量需要在类外面进行初始化
template<bool threads,int inst>
typename _DefaultAllocTemplate<threads, inst>::Obj*
_DefaultAllocTemplate<threads, inst>::FreeList[__NFREELISTS] = { 0 };
template<bool threads,int inst>
char*_DefaultAllocTemplate<threads, inst>::_StartFree = 0;
template<bool threads, int inst>
char*_DefaultAllocTemplate<threads, inst>::_EndFree = 0;
template<bool threads, int inst>
size_t _DefaultAllocTemplate<threads, inst>::_HeapSize = 0;
trase配置文件:
有的时候我们希望借助一些辅助信息来还原我们代码的逻辑,一级得到一些我们想要的一些信息,我们就可以借助配置文件来完成:
Trase.h文件:
#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* function,
const char * filename, int line, char* format, ...)
{
// 读取配置文件
#ifdef __DEBUG__
// 输出调用函数的信息
fprintf(stdout, "[ %s:%d]%s", GetFileName(filename).c_str(), line, function);
// 输出用户打的trace信息
va_list args;
va_start(args, format);
vfprintf(stdout, format, args);
va_end(args);
#endif
}
#define __TRACE_DEBUG(...) \
__trace_debug(__FUNCTION__, __FILE__, __LINE__, __VA_ARGS__);
.cpp文件:
#include<iostream>
#include"Alloc.h"
using namespace std;
int main()
{
void *p1 = _DefaultAllocTemplate<false, 1>::Allocate(129);
_DefaultAllocTemplate<false, 1>::Dellocate(p1, 129);
void *p2 = _DefaultAllocTemplate<false, 1>::Allocate(8);
_DefaultAllocTemplate<false, 1>::Dellocate(p2, 8);
system("pause");
return 0;
}
关于这次的空间配置器就总结到这里,当然第一次分析源码,作为小白还有很多欠缺的地方,希望小伙伴们轻喷啊.