夥伴算法
大概是這樣的,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;
}