伙伴算法
大概是这样的,monkey储存引擎(今年猴年了嘛~取个名字,重了再换)的内存管理使用伙伴算法,大概原理是这样的:
1.多个空闲内存块的链表,分别是不同大小的内存块,1K,2K,4K,8K,…,,按照这样的情况一直到4M,当程序申请内存时,选择一个稍大于其所要申请的块,比如申请1023字节,给1K,1024字节也给1K(为什么呢– 后文会讲),2056字节给4K。
2.如果要给的内存块没有,比如要分配4K,但是4K大小的内存块用光了,那么向上,将8K大小的分割掉,拿一个4K的分配掉,剩余的4K插入到4K的链表里面去,当一直分割,4M的最大块也没有的情况,就向操作系统申请4M的块。
3.不用的内存如何回收呢?因为给的内存块大小大于所储存的内容,所以将第一个字节用来记录内存块大小,回收的时候根据第一字节的大小扔进链表就可以啦~
PS:良好的代码结构,可以让代码重用,并减少耦合,分情况将各个功能写成短小精悍的小函数,互相调用,既增加可读性,又增加可维护性,还不容易出bug~这就是UNIX的组织思想吧~
代码实现
//storage.h
#ifndef STORAGE_H_INCLUDED
#define STORAGE_H_INCLUDED
#include "link.h"
#define SEGMENT_SIZE 1024
#define INIT_SEGMENT_NUM 32
#define MAX_FREE_PART 13 //最大2^13大小的块
typedef struct { //伙伴系统
LinkNode partner[MAX_FREE_PART]; //2^0~2^13=4096 空头链表
}FreeList;
FreeList freeList;
//初始时初始化INIT_SEGMENT_NUM个段,如果新插入的记录能够放进段中,则放入,每个段最多放一个,如果放不进去,则放入页中
void InitStorage(); //初始化储存引擎
void* InsertToFreeList(unsigned int size,void * pData); //向储存引擎插入数据
void Free(void* pData); //回收内存
#endif // STORAGE_H_INCLUDED
//link.h
#ifndef LINK_H_INCLUDED
#define LINK_H_INCLUDED
typedef struct _LinkNode{
void* pData; //链表内容
struct _LinkNode * next; //下一个节点
} LinkNode;
#endif // LINK_H_INCLUDED
//storage.c
#include "storage.h"
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
void* GetFree(unsigned int);
void InsertFree(unsigned n,void *pData) //向2^n*seg区插入一段空闲空间
{
LinkNode* p = (LinkNode*)malloc(sizeof(LinkNode)); //插入链表第一个节点
p->pData = pData;
p->next = freeList.partner[n].next;
freeList.partner[n].next = p;
return;
}
void* Splice(unsigned int n) //分裂节点并返回分裂出的一个节点的指向内存,另一个节点插入链表相应位置
{
if(n > MAX_FREE_PART) //请求大于最大支持的内存块
{
LinkNode* p = &freeList.partner[MAX_FREE_PART - 1]; //是否是最大的块内存不足
if(p->next) //最大的块还有剩余~说明请求的内存太大了,不支持
{
fprintf(stderr,"Max support memory part is 2 ^ %d!\n",MAX_FREE_PART);
exit(-1);
}
//分裂超出最大块出现在最大块已经不足的情况下,那么返回新申请的最大块给他即可
return malloc(SEGMENT_SIZE << MAX_FREE_PART);
}
else
{
void* pBig = GetFree(n); //索取n的空间并分裂
void* pHalf = pBig + (SEGMENT_SIZE << (n-1));
InsertFree(n-1,pHalf);
return pBig;
}
}
void* GetFree(unsigned int n) //向空闲链表索取n*segment的空间
{
LinkNode* p = &freeList.partner[n];
if(p->next) //第一个节点就有空间
{
LinkNode *tn = p->next;
void *t = tn->pData;
p->next = p->next->next; //取出并返回
free(tn);
return t;
}
else
{
return Splice(n+1); //要求分裂更大的块
}
}
void* InsertToFreeList(unsigned int size,void * pData) //使用空闲空间链表的一小段空间
{
unsigned int n = size / SEGMENT_SIZE;
unsigned int i = 0;
while(n)
{
n >>= 1;
i++;
}
void *p = GetFree(n); //获取空间
memset(p,i,1); //空间第一个字节写入空间大小,为了垃圾回收
memcpy(p+1,pData,size); //其余空间写入数据
return p;
}
void Free(void* pData)
{
unsigned int n = *((char*)pData); //读出第一个字节表示的该段内存大小
InsertFree(n,pData); //回收内存
}
void InitStorage()
{
//TODO:初始化一些小的内存片段供使用
}
经测试,缓存池分配10000次内存比直接调用malloc分配10000次快120倍左右
另外补充将小块内存合并的代码:
void InsertFree(unsigned n,void *pData) //向2^n*seg区插入一段空闲空间
{
if(n > MAX_FREE_PART)
{
free(pData);
return;
}
LinkNode* t = &freeList.partner[n];
LinkNode* parent = t;
t = t->next;
while(t && t->pData < pData) // 寻找相邻块并合并
{
if(t->pData + (SEGMENT_SIZE << n) == pData)
{
parent->next = t->next;
//printf("combine");
InsertFree(n+1,t->pData); //Combine
free(t);
return;
}
parent = t;
t = t->next;
}
if(t && t->pData == pData + (SEGMENT_SIZE << n))
{
//printf("combine");
InsertFree(n+1,pData); //合并并插入上个链表
}
else
{
if(!t)
{
LinkNode* p = malloc(sizeof(LinkNode));
p->next = NULL;
p->pData = pData;
parent->next = p;
}
else
{
LinkNode* p = malloc(sizeof(LinkNode));
p->next = t->next;
p->pData = pData;
t->next = p;
}
}
return;
}