C++实现的内存池

原文地址:https://blog.csdn.net/shawngucas/article/details/6574863

只能感叹写这个的大佬牛X…
自己写了一点注释,还是有些地方没有看懂,但是感觉也有些地方有点问题。
这里只转载了用链表实现的部分。

1. 内存池设计

1.1 目的

在给定的内存buffer上建立内存管理机制,根据用户需求从该buffer上分配内存或者将已经分配的内存释放回buffer中。

1.2 要求

尽量减少内存碎片,平均效率高于C语言的malloc和free。

1.3 设计思路

将buffer分为四部分,第1部分是mem_pool结构体;第2部分是内存映射表;第3部分是内存chunk结构体缓冲区;第4部分是实际可分配的内存区。整个buffer结构图如下图所示:
在这里插入图片描述
第1部分的作用是可以通过该mem_pool结构体控制整个内存池。

第2部分的作用是记录第4部分,即实际可分配的内存区的使用情况。表中的每一个单元表示一个固定大小的内存块(block),多个连续的block组成一个chunk,每个block的详细结构如下图所示:
在这里插入图片描述
第3部分是一个mem_chunk pool,其作用是存储整个程序可用的mem_chunk结构体。mem_chunk pool中的mem_chunk被组织成双向链表结构(快速插入和删除)。每个mem_chunk结构图如下图所示:
在这里插入图片描述
其中pmem_block指向该chunk在内存映射表中的位置,others表示其他一些域,不同的实现对应该域的内容略有不同。

第4部分就是实际可以被分配给用户的内存。

整个内存池管理程序除了这四部分外,还有一个重要的内容就是memory chunk set。虽然其中的每个元素都来自mem_chunk pool,但是它与mem_chunk pool的不同之处在于其中的每个memory chunk中记录了当前可用的一块内存的相关信息。而mem_chunk pool中的memory chunk的内容是无定以的。可以这样理解mem_chunk pool与memory chunk set:mem_chunk pool是为memory chunk set分配内存的“内存池”,只是该“内存池”每次分配的内存大小是固定的,为mem_chunk结构体的大小。内存池程序主要是通过搜索这个memory chunk set来获取可被分配的内存。在memory chunk set上建立不同的数据结构就构成了不同的内存池实现方法,同时也导致了不同的搜索效率,直接影响内存池的性能,本文稍后会介绍两种内存池的实现。(后面一个可以去看原文的实现啦)

1.4 内存池管理程序运行过程

  • 初始化:内存映射表中只有一块可用的内存信息,大小为内存池中所有可用的内存。从memory chunk pool中分配一个mem_chunk,使其指向内存映射表中的第一个block,并根据具体的内存池实现方式填充mem_chunk中的其他域,然后将该mem_chunk添加到memory chunk set中。
  • 申请内存:当用户申请一块内存时,首先在memory chunk set中查找合适的内存块。如果找到符合要求的内存块,就在内存映射表中找到相应的chunk,并修改chunk中相应block结构体的内容,然后根据修改后的chunk修改memory chunk set中chunk的内容,最后返回分配内存的起始地址;否则返回NULL。
  • 释放内存:当用户释放一块内存时,首先根据这块内存的起始地址找到其在内存映射表中对应的chunk,然后尝试将该chunk和与其相邻的chunk合并,修改chunk中相应block的内容并修改memory chunk set中相应chunk的内容或者向memory chunk set加入新的mem_chunk(这种情况在不能合并内存是发生)。

1.5 减少内存碎片

本文设计的方法只能在一定程度上减少内存碎片,并不能彻底消除内存碎片。具体方法如下:

在用户释放内存时,尝试将该内存与其相邻的内存合并。如果其相邻内存为未分配内存则合并成功,合并后作为一整块内存使用;如火其相邻内存为已分配内存则不能合并,该释放的内存块作为一个独立的内存块被使用。

2 内存池实现-链表结构

绿色表示未使用的内存,红色表示已经使用的内存。其中每个block表示64B,这个值可以根据具体需要设定。

扫描二维码关注公众号,回复: 5821468 查看本文章
  • 初始化
    在这里插入图片描述

  • 申请内存
    在这里插入图片描述
    在这里插入图片描述

  • 释放内存

在这里插入图片描述
在这里插入图片描述

3 大顶堆以及性能比较可以参考原博文

4

这里给出我自己稍作修改的版本和自己的测试程序
增加了一个特性,如果剩余空间不足的话,就重新malloc

Memory_pool.h

#ifndef _Memory_pool_H  
#define _Memory_pool_H  
#include <stdlib.h>  
#define MINUNITSIZE 64  
#define ADDR_ALIGN 8    //对齐的字节

using namespace std;

struct memory_chunk; 
struct memory_block{  
    size_t count;                  //
    size_t start; 
    memory_chunk* pmem_chunk; 
}; 
// 可用的内存块结构体  
struct memory_chunk{
    memory_block* pfree_mem_addr; 
    memory_chunk* pre; 
    memory_chunk* next; 
}; 
// 内存池结构体  
struct memorypool{  
    void *memory;                   //实际存储单元
    size_t size;                    //实际用来存储的大小
    memory_block* pmem_map;         //已分配内存映射
    memory_chunk* pfree_mem_chunk;  //指向空闲空间的指针
    memory_chunk* pchunk_pool;      //memory_chunk池
    size_t used_size;               //记录内存池中已经分配给用户的内存的大小
    size_t rest_size;               //记录剩余的内存大小,实际上还要减去size_offset
    size_t size_offset;             //chunk_pool,manager,pmem_map的空间之和
    size_t pool_cnt;                //记录memory_chuck pool中剩余chunk个数 
    size_t free_chunk_cnt;          //记录空闲的内存块的数目  
    size_t block_cnt;               // 一个 mem_unit 大小为 MINUNITSIZE  
};

size_t check_align_addr(void*& pBuf);   //pBuf大小补齐到ADDR_ALIGN字节
size_t check_align_block(size_t size);  //size大小减到MINUNITSIZE倍数字节
size_t check_align_size(size_t size);   //size大小补齐到SIZE_ALIGN字节
memory_chunk* create_list(memory_chunk* pool, size_t count);   //创建长度为count的双向链表
memory_chunk* pop_front(memory_chunk*& pool);                  //弹出链表头部指针
void push_back(memory_chunk*& head, memory_chunk* element);    //向链表尾部添加一个元素
void push_front(memory_chunk*& head, memory_chunk* element);   //向链表头部添加一个元素
void delete_chunk(memory_chunk*& head, memory_chunk* element); //删除某个元素
void add_new_chunk(memorypool *&pMem,memory_block *&current_block);// 添加一个chunk指向currentblock
void* index2addr(memorypool* mem_pool, size_t index);          //内存映射表中的索引转化为内存起始地址
size_t addr2index(memorypool* mem_pool, void* addr);           //内存起始地址转化为内存映射表中的索引  
memorypool* CreateMemoryPool(size_t sBufSize); 
void ReleaseMemoryPool(memorypool** ppMem);  
void* GetMemory(size_t sMemorySize, memorypool* pMem); 
void FreeMemory(void *ptrMemoryBlock, memorypool* pMem); 
  
#endif //_Memory_pool_H 

Memory_pool.cpp

#include<memory.h>  
#include<cstdio>
#include "Memory_pool.h"  
using namespace std;
size_t check_align_addr(void*& pBuf){
    size_t align=0;
    size_t addr=reinterpret_cast<size_t>(pBuf);
    align=ADDR_ALIGN-addr%ADDR_ALIGN;
    pBuf=(char*)pBuf+align;
    return align;
}  
size_t check_align_block(size_t size){
    size_t align=size%MINUNITSIZE;
    return size-align;
}  
size_t check_align_size(size_t size){
    size=(size+MINUNITSIZE-1)/MINUNITSIZE*MINUNITSIZE;
    return size;
}  
/************************************************************************/  
/* 以下是链表相关操作 */
/************************************************************************/  

/*   <--next--
    pool    head .... 内存增长方向
      --pre-->
*/
memory_chunk* create_list(memory_chunk* pool, size_t count){
    if(pool==nullptr){//未申请内存
        return nullptr;
    }  
    memory_chunk* head=nullptr;
    for(size_t i=0;i<count;i++){
        pool->pre=nullptr;
        pool->next=head;
        if(head!=nullptr){
            head->pre=pool;
        }  
        head=pool;
        pool++;//?
    }  
    return head;
}  
memory_chunk* pop_front(memory_chunk*& pool){
    if(!pool){
        return nullptr;
    }  
    memory_chunk* tmp=pool;
    pool=tmp->next;
    pool->pre=nullptr;
    return  tmp;
}  
void push_back(memory_chunk*& head, memory_chunk* element){
    if(head==nullptr){//链表为空时 
        head=element;
        head->pre=element;
        head->next=element;
        return;
    }  
    head->pre->next=element;
    element->pre=head->pre;
    head->pre=element;
    element->next=head;
}  
void push_front(memory_chunk*& head, memory_chunk* element){ 
    element->pre=nullptr;
    element->next=head;
    if(head!=nullptr){
        head->pre=element;
    }  
    head=element;
}  
void delete_chunk(memory_chunk*& head, memory_chunk* element){
    // 在双循环链表中删除元素  
    if(element==nullptr){
        return;
    }  
    // element为链表头  
    else if(element==head){
        // 链表只有一个元素  
        if(head->pre==head){
            head=nullptr;
        }  
        else{
            head=element->next;
            head->pre=element->pre;
            head->pre->next=head;
        }  
    }  
    // element为链表尾  
    else if(element->next==head){
        head->pre=element->pre;
        element->pre->next=head;
    }  
    else{
        element->pre->next=element->next;
        element->next->pre=element->pre;
    }  
    element->pre=nullptr;
    element->next=nullptr;
}  

void* idx2addr(memorypool* mem_pool, size_t idx){
    char* p=(char*)(mem_pool->memory);
    void* ret=(void*)(p+idx*MINUNITSIZE);
      
    return ret;
}  

size_t addr2idx(memorypool* mem_pool, void* addr){
    char* start=(char*)(mem_pool->memory);
    char* p=(char*)addr;
    size_t idx=(p-start)/MINUNITSIZE;
    return idx;
}  
/************************************************************************/  
/* 生成内存池 
* pBuf: 给定的内存buffer起始地址 
* sBufSize: 给定的内存buffer大小 
* 返回生成的内存池指针*/
/************************************************************************/  
memorypool* CreateMemoryPool(size_t sBufSize){ 
    void* pBuf=malloc(sBufSize);
    memset(pBuf,0,sBufSize);
    memorypool* mem_pool=(memorypool*)pBuf;
    // 计算需要多少memory map单元格

    size_t mempool_size=sizeof(memorypool);//管理单元大小
    size_t block_size=sizeof(memory_block);
    size_t chunk_size=sizeof(memory_chunk);

    mem_pool->pool_cnt=(sBufSize-mempool_size+MINUNITSIZE-1)/MINUNITSIZE;//取上整
    mem_pool->pmem_map=(memory_block*)((char*)pBuf+mempool_size);
    //计算chuck_pool首地址
    mem_pool->pchunk_pool=(memory_chunk*)((char*)pBuf+mempool_size+block_size*mem_pool->pool_cnt);
    //计算实际存储单元的首地址
    mem_pool->size_offset=mempool_size+block_size*mem_pool->pool_cnt+chunk_size*mem_pool->pool_cnt;
    mem_pool->memory=(char*)pBuf+mem_pool->size_offset;
    //计算实际用来存储的大小 
    mem_pool->size=sBufSize-mempool_size-block_size*mem_pool->pool_cnt-chunk_size*mem_pool->pool_cnt;
    size_t align=check_align_addr(mem_pool->memory);//实际存储单元向后移动align个字节
    mem_pool->size-=align;//大小减去align(产生了碎片)
    mem_pool->size=check_align_block(mem_pool->size);//按MINUNITSIZE字节对齐,使得有整数个block  
    mem_pool->block_cnt=mem_pool->size/MINUNITSIZE;//计算含有多少个block
    //创建chunk_pool链表
    mem_pool->pchunk_pool=create_list(mem_pool->pchunk_pool, mem_pool->pool_cnt);
    //初始化 pfree_mem_chunk,双向循环链表  
    memory_chunk* tmp=pop_front(mem_pool->pchunk_pool);//拿出链表的第一个元素用来初始化
    tmp->pre=tmp;
    tmp->next=tmp;
    tmp->pfree_mem_addr=nullptr;
    mem_pool->pool_cnt--;
      
    // 初始化 pmem_map  
    mem_pool->pmem_map[0].count=mem_pool->block_cnt;
    mem_pool->pmem_map[0].pmem_chunk=tmp;
    mem_pool->pmem_map[mem_pool->block_cnt-1].start=0;
    
    tmp->pfree_mem_addr=mem_pool->pmem_map;
    push_back(mem_pool->pfree_mem_chunk,tmp);
    mem_pool->free_chunk_cnt=1;
    mem_pool->used_size=0;
    mem_pool->rest_size=sBufSize;
    return mem_pool;
}  
void ReleaseMemoryPool(memorypool** ppMem){}  
/************************************************************************/  
/* 从内存池中分配指定大小的内存  
* pMem: 内存池 指针 
* sMemorySize: 要分配的内存大小 
* 成功时返回分配的内存起始地址,失败返回nullptr */
/************************************************************************/  
void* GetMemory(size_t sMemorySize, memorypool* pMem){
    //如果剩余部分不足就重新申请空间
    if(sMemorySize>pMem->rest_size-pMem->size_offset)
        return malloc(sMemorySize);
    
    sMemorySize=check_align_size(sMemorySize);
    size_t idx=0;
    memory_chunk* tmp=pMem->pfree_mem_chunk;
    for(idx=0;idx<pMem->free_chunk_cnt;idx++){//最佳优先分配
        if(tmp->pfree_mem_addr->count*MINUNITSIZE>=sMemorySize){ 
            break;
        }  
        tmp=tmp->next;
    }  
    if(idx==pMem->free_chunk_cnt){//如果到了最后一个,说明没有足够的空间可以分配
        return nullptr;
    }
    pMem->used_size+=sMemorySize;//使用了的内存加sMemorySize
    pMem->rest_size-=sMemorySize;//剩余内存减了sMemorySize
    if(tmp->pfree_mem_addr->count*MINUNITSIZE==sMemorySize){
        // 当要分配的内存大小与当前chunk中的内存大小相同时,从pfree_mem_chunk链表中删除此chunk  
        size_t current_idx=(tmp->pfree_mem_addr-pMem->pmem_map);
        delete_chunk(pMem->pfree_mem_chunk,tmp);
        tmp->pfree_mem_addr->pmem_chunk=nullptr;

        push_front(pMem->pchunk_pool,tmp);//归还memory_chunk
        pMem->free_chunk_cnt--;
        pMem->pool_cnt++;
        
        return idx2addr(pMem,current_idx);//返回地址
    }  
    else{
        // 当要分配的内存小于当前chunk中的内存时,更改pfree_mem_chunk中相应chunk的pfree_mem_addr  
          
        // 复制当前memory_block  
        memory_block copy;
        copy.count=tmp->pfree_mem_addr->count;
        copy.pmem_chunk=tmp;
        // 记录该block的起始和结束索引  
        memory_block* current_block=tmp->pfree_mem_addr;
        size_t current_idx=(current_block-pMem->pmem_map);

        current_block->count=sMemorySize/MINUNITSIZE;
        pMem->pmem_map[current_idx+current_block->count-1].start=current_idx;//改变最后一个单元的start
        current_block->pmem_chunk=nullptr;// ?nullptr表示当前内存块已被分配  
        
        // 当前block被一分为二,更新剩下的block中的内容
        pMem->pmem_map[current_idx+current_block->count].count=copy.count-current_block->count;
        pMem->pmem_map[current_idx+current_block->count].pmem_chunk=copy.pmem_chunk;//保持在链表中的位置
        // 更新原来的pfree_mem_addr
        tmp->pfree_mem_addr=&(pMem->pmem_map[current_idx+current_block->count]);
      
        size_t end_idx=current_idx+copy.count-1;
        pMem->pmem_map[end_idx].start=current_idx+current_block->count;
        return idx2addr(pMem, current_idx);
    }     
}
/************************************************************************/  
/* 从内存池中释放申请到的内存 
* pMem:内存池指针 
* ptrMemoryBlock:申请到的内存起始地址 */
/************************************************************************/  
void add_new_chunk(memory_block *&current_block,memorypool *&pMem){
    memory_chunk* new_chunk=pop_front(pMem->pchunk_pool);
    new_chunk->pfree_mem_addr=current_block;
    current_block->pmem_chunk=new_chunk;
    push_back(pMem->pfree_mem_chunk, new_chunk);//使用栈上的内存?
    pMem->pool_cnt--;
    pMem->free_chunk_cnt++;
}
void FreeMemory(void *ptrMemoryBlock,memorypool* pMem){
    size_t current_addr=reinterpret_cast<size_t>(ptrMemoryBlock);
    size_t mem_addr=reinterpret_cast<size_t>(ptrMemoryBlock);
    size_t all_size=pMem->rest_size+pMem->used_size;
    // 如果这个内存不在内存池内
    if(current_addr<mem_addr||current_addr>=mem_addr+all_size){
        free(ptrMemoryBlock);
        return;
    }

    size_t current_idx=addr2idx(pMem,ptrMemoryBlock);
    size_t size=pMem->pmem_map[current_idx].count*MINUNITSIZE;
    // 判断与当前释放的内存块相邻的内存块是否可以与当前释放的内存块合并  
    memory_block* pre_block=nullptr;
    memory_block* next_block=nullptr;
    memory_block* current_block=&(pMem->pmem_map[current_idx]);
    // 第一个  
    if(current_idx==0){
        next_block=&(pMem->pmem_map[current_idx+current_block->count]);
        if(current_block->count<pMem->block_cnt&&next_block->pmem_chunk!=nullptr){//?为什么会比block_cnt大
            
            // 如果后一个内存块是空闲的,合并  
            next_block->pmem_chunk->pfree_mem_addr=current_block;
            pMem->pmem_map[current_idx+current_block->count+next_block->count-1].start=current_idx;
            current_block->count+=next_block->count;
            current_block->pmem_chunk=next_block->pmem_chunk;
            next_block->pmem_chunk=nullptr;
        }  
        else{
            add_new_chunk(current_block,pMem);
        }         
    }  
    // 最后一个  
    else if(current_idx==pMem->block_cnt-1){
        pre_block=&(pMem->pmem_map[current_idx-1]);
        if(current_block->count<pMem->block_cnt&&pre_block->pmem_chunk!=nullptr){
            size_t idx=pre_block->count;
            pre_block=&(pMem->pmem_map[idx]);
              
            // 如果前一个内存块是空闲的,合并  
            pMem->pmem_map[current_idx+current_block->count-1].start=current_idx-pre_block->count;
            pre_block->count+=current_block->count;
            current_block->pmem_chunk=nullptr;
            // 如果前一块内存不是空闲的,在pfree_mem_chunk中增加一个chunk  
        }  
        else{
            add_new_chunk(current_block,pMem);
        }  
    }  
    else{ 
        next_block=&(pMem->pmem_map[current_idx+current_block->count]);
        pre_block=&(pMem->pmem_map[current_idx-1]);
        size_t idx=pre_block->start;
        pre_block=&(pMem->pmem_map[idx]);
        bool is_back_merge=false;
        if(next_block->pmem_chunk==nullptr && pre_block->pmem_chunk==nullptr){
            add_new_chunk(current_block,pMem);
        }  
        // 后一个内存块  
        if(next_block->pmem_chunk!=nullptr){
            next_block->pmem_chunk->pfree_mem_addr=current_block;
            pMem->pmem_map[current_idx+current_block->count+next_block->count-1].start=current_idx;
            current_block->count+=next_block->count;
            current_block->pmem_chunk=next_block->pmem_chunk;
            next_block->pmem_chunk=nullptr;
            is_back_merge=true;
        }  
        // 前一个内存块  
        if(pre_block->pmem_chunk!=nullptr){
            pMem->pmem_map[current_idx+current_block->count-1].start=current_idx-pre_block->count;
            pre_block->count+=current_block->count;
            if(is_back_merge){
                delete_chunk(pMem->pfree_mem_chunk, current_block->pmem_chunk);
                push_front(pMem->pchunk_pool, current_block->pmem_chunk);
                pMem->free_chunk_cnt--;
                pMem->pool_cnt++;
            }  
            current_block->pmem_chunk=nullptr;
        }         
    }  
    pMem->used_size-=size;
    pMem->rest_size+=size;
}  

test.cpp

#include<bits/stdc++.h>
#include<sys/time.h>
#include"Memory_pool/Memory_pool.h"
typedef unsigned long long ull;
const int N=1e8+5;
const size_t msize=512*1024*1024;
using namespace std;
int num[N],*p[N],*q[N];
ull Get_now(){
    #ifdef _MSC_VER
        _timeb timebuffer;
        _ftime(&timebuffer);
        ull ret=timebuffer.time;
        ret=ret*1000+timebuffer.millitm;
        return ret;
    #else
        timeval tv;         
        gettimeofday(&tv,0);
        ull ret=tv.tv_sec;
        return ret*1000+tv.tv_usec/1000;
    #endif
}
int main(){
    ull t1=Get_now();
    Memory_pool* mpool=CreateMemoryPool(msize);
    for(int i=0;i<N;i++){
        num[i]=i;
        p[i]=(int*)GetMemory(sizeof(int),mpool);
        *p[i]=num[i];
        FreeMemory(p[i],mpool);
    }
   cout<<Get_now()-t1<<endl;
   // cout<<*p[123234]<<endl;
    t1=Get_now();
    for(int i=0;i<N;i++){
        num[i]=i;
        q[i]=(int*)malloc(sizeof(int));
        *q[i]=num[i];
        free(q[i]);
    }
    cout<<Get_now()-t1<<endl;
   // cout<<*q[123734]<<endl;
    return 0;
}

测试程序申请了1e8次int的内存,以malloc和内存池作比较,感觉没有快多少,只快了不到30%(也可能是我的测试方法有问题

运行结果
在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/Monster_ixx/article/details/88895962