内存池原理剖析与手撕

蒸汽
蒸汽
发布于 2024-12-14 / 14 阅读
0
1

内存池原理剖析与手撕

⚾️概述

是什么:是对内存块的管理组件

作用:

  • 对整块的内存进行集中管理。

  • 避免了频繁向OS调用malloc

  • 避免了内存碎片

🥏C++ STL内存池实现

在C++STL库中,采用哈希表和定长内存池结合的方式实现了一个内存池。具体如下

构造多个定长内存池,以一个固定的对齐数进行对齐(例如以8字节进行对齐),第一个定长内存池的内存对象大小为8(至少得能保证无论在64位还是32位系统下都可以保存下一个指针类型),第二个内存池对象大小为16…最后一个内存池对象大小为128byte,当申请的内存大小超过128字节时,通过二级空间配置器申请(直接从内存中申请)。

构造一个哈希表,将不同大小的内存对象挂在哈希表中。如下图: image.png 申请内存:加入要申请的内存大小为8字节直接在index = 0处分配一块内存,当然申请的内存小于8字节时也会直接分配8字节的内存。当Free_list[index]为nullptr时从内存中申请一块内存,切割成固定大小‘挂在"Free_list[index]位置。

释放内存:根据内存对象大小,计算index在插入到哈希表中的index位置。

🥎源码实现

ps:内存池难点在于对指针的灵活应用!!! (线程池在于线程安全性)

ps:若无法理解==*(char**)==的含义,请阅读本站《对指针的回炉重造》

🪃1.实现定长的内存池

可随时回收,但只能定长

#include <stdio.h>
#include <stdlib.h>
​
//
#define MEM_PAGE_SIZE   4096
​
typedef struct mempool_s {
    int block_size;
    int free_count;
​
    char *free_ptr;
    char *mem;
} mempool_t;
​
​
int mp_init(mempool_t *m, int size) {
​
    if (!m) return -1;
    if (size < 16) size = 16;
​
    m->block_size = size;
​
    m->mem = (char *)malloc(MEM_PAGE_SIZE);
    if (!m->mem) return -1;
    m->free_ptr = m->mem;
    m->free_count = MEM_PAGE_SIZE / size;
​
    int i = 0;
    char *ptr = m->free_ptr;
    for (i = 0;i < m->free_count;i ++) {
        *(char **)ptr = ptr + size;
        ptr += size;
    }
    *(char **)ptr = NULL;
​
    return 0;
}
​
void mp_dest(mempool_t *m) {
    if (!m || !m->mem) return ;
​
    free(m->mem);
​
}
​
​
void *mp_alloc(mempool_t *m) {
​
    if (!m || m->free_count == 0) return NULL;
​
    void *ptr = m->free_ptr;
​
    m->free_ptr = *(char **)ptr;
    m->free_count --;
​
    return ptr;
}
​
​
void mp_free(mempool_t *m, void *ptr) {
    //让ptr指向的内存块node0指向free_ptr
    //再让free_ptr指向node0
    //实际上就是让node0插入
    *(char **)ptr = m->free_ptr;
    m->free_ptr = (char *)ptr;
    m->free_count ++;
}
​
​
int main() {
​
    mempool_t m;
​
    mp_init(&m, 32);
​
    void *p1 = mp_alloc(&m);
    printf("1: mp_alloc: %p\n", p1);
​
    void *p2 = mp_alloc(&m);
    printf("2: mp_alloc: %p\n", p2);
​
    void *p3 = mp_alloc(&m);
    printf("3: mp_alloc: %p\n", p3);
​
    void *p4 = mp_alloc(&m);
    printf("4: mp_alloc: %p\n", p4);
​
    mp_free(&m, p2);
​
    void *p5 = mp_alloc(&m);
    printf("5: mp_alloc: %p\n", p5);
​
​
    return 0;
}
​
​
//答案
​
1: mp_alloc: 0x138008800
2: mp_alloc: 0x138008820
3: mp_alloc: 0x138008840
4: mp_alloc: 0x138008860
5: mp_alloc: 0x138008820
​
​

🛼2.变长内存池

可以变长分配,但只能统一回收

​
​
#include <stdio.h>
#include <stdlib.h>
​
#define MEM_PAGE_SIZE   4096
​
​
​
typedef struct mp_node_s {
  char *free_ptr;
  char *end;
  struct mp_node_s *next;
} mp_node_t;
​
​
typedef struct mp_pool_s {
  struct mp_node_s *first;
  struct mp_node_s *current;
  int max; // page size
} mp_pool_t;
​
​
int mp_init(mp_pool_t *m, int size) {
​
  if (!m) return -1;
​
  void *addr = malloc(size); // 4096
  mp_node_t *node = (mp_node_t*)addr;
  
  node->free_ptr = (char*)addr + sizeof(mp_node_t);
  node->end = (char*)addr + size;
  node->next = NULL;
​
  m->first = node;
  m->current = node;
  m->max = size;
​
  return 0;
​
}
​
​
void mp_dest(mp_pool_t *m) {
​
  if (!m) return ;
​
  while (!m->first) {
​
    void *addr = m->first;
    mp_node_t *node = (mp_node_t*)addr;
​
    m->first = node->next;
​
    free(addr);
  }
  
  return ;
}
​
void *mp_alloc(mp_pool_t *m, int size) {   //size < (4096-sizeof(mp_node_t))
​
  void *addr = m->current;
  mp_node_t *node = (mp_node_t*)addr;
​
  do {
    if (size <= (node->end - node->free_ptr)) { //
​
      char *ptr = node->free_ptr;
      node->free_ptr += size;
​
      return ptr;
      
    }
    node = node->next;
    
  } while (node);
​
  // new node
​
  addr = malloc(m->max); // 4096
  node = (mp_node_t*)addr;
​
  node->free_ptr = (char*)addr + sizeof(mp_node_t);
  node->end = (char*)addr + m->max;
​
  node->next = m->current;
  m->current = node;
​
  char *ptr = node->free_ptr;
  node->free_ptr += size;
​
  return ptr;
​
}
​
​
void mp_free(mp_pool_t *m, void *ptr) {
​
}
​
​
​
int main() {
​
  
  mp_pool_t m;
​
  mp_init(&m, MEM_PAGE_SIZE);
​
​
  void *p1 = mp_alloc(&m, 16);
  printf("1: mp_alloc: %p\n", p1);
​
  void *p2 = mp_alloc(&m, 32);
  printf("2: mp_alloc: %p\n", p2);
​
  void *p3 = mp_alloc(&m, 64);
  printf("3: mp_alloc: %p\n", p3);
​
  void *p4 = mp_alloc(&m, 128);
  printf("4: mp_alloc: %p\n", p4);
​
  void *p5 = mp_alloc(&m, 256);
  printf("5: mp_alloc: %p\n", p5);
​
  mp_dest(&m);
​
}
​
​
#运行结果
​
1: mp_alloc: 0x137008818
2: mp_alloc: 0x137008828
3: mp_alloc: 0x137008848
4: mp_alloc: 0x137008888
5: mp_alloc: 0x137008908

🛹3. 双链表内存池实现

整体设计

  • 内存池结构

两个链表,RequestMemoryReleaseMemory

RequestMemory链表存储的是使用new或者malloc从物理内存申请的还没有被使用的内存块,是一个个的memNode节点。

ReleaseMemory链表存储的是使用完释放回来的固定大小的内存块。

申请内存

  • 先在ReleaseMemory找,如果有内存则直接pop使用

  • ReleaseMemory为nullptr时,在RequestMemory中找。

  • RequestMemory的头节点表示的是新申请的,申请内存时只需要在头结点中找,判断头结点的useCount和sumCount是否相等。当useCount等于sumCount时表示已经用完了,就需要去物理内存中申请,否则直接从表头push一块。

  • 去物理内存申请内存时,申请的大小是上一次申请内存块大小的二倍,并将申请的内存块push到RequestMemory头部。

释放内存

释放内存时,直接将要释放的内存push到ReleaseMemory的头部即可。

image.png

#include <iostream>
​
using namespace std;
​
#define MEM_PAGE_SIZE 4096
​
class MemPool {
public:
    //这个是requestmemory的节点
    struct MemBlock {
        // 这条一定要放在第一位,为了后面双指针**
        void *ptr;    //block内存的首地址
        MemBlock *next;//
        size_t _objNum;//内存块对象的数量
        size_t _useCount;
        MemBlock(size_t num) : _objNum(num), next(nullptr), _useCount(0) {
            ptr = malloc(_objNum * _size);
        }
    };
​
    MemPool() {
        //初始化的时候应该初始化Requestmem?
        _maxSize = 4096;
        ReleaseMemory = nullptr;
        RequestMemory = new MemBlock(MEM_PAGE_SIZE);
        cout <<"调用MemPool构造函数"<< endl;
    }
​
​
    ~MemPool() {
        cout << "调用mempool析构函数~()" << endl;
        //释放的时候两个链表一起释放
        //先释放RequestMemory
        MemBlock *tmp = RequestMemory;
        while (tmp != nullptr) {
            MemBlock *del = tmp;
            tmp = tmp->next;
            // del->~MemBlock();//析构block
            delete del; //会自动调用~memblock()
        }
        //todo:再释放ReleaseMemory?需要释放这个吗?这个全部都是void*的内存
    }
    void* allocate() {
        //分配内存先从releaseMemory找
        if (ReleaseMemory != nullptr) {
            void *ptr = *(void**)RequestMemory;
             ReleaseMemory = *(void**)ReleaseMemory; //ReleaseMemory前几个字节存储的是下一个node的地址
            return ptr;
        }
        //releasemem为空,则去requestmem中拿,如果拿不到就申请两倍
        if (RequestMemory->_objNum == RequestMemory->_useCount) {
            int new_objNum = RequestMemory->_useCount * 2;
            RequestMemory->_objNum = new_objNum > _maxSize ? _maxSize : new_objNum;
            MemBlock *newBlock = new MemBlock(RequestMemory->_objNum);
            //把这个newblock挂到RequestMem头部
            newBlock->next = RequestMemory;
            RequestMemory = newBlock;
            //把usecount重置
            RequestMemory->_useCount = 0;
        }
        //分配内存
        void *ptr = (char *)RequestMemory + RequestMemory->_useCount * _size;
        RequestMemory->_useCount++;
        return ptr;
    }
    void deallocate(void* p) {
        if (p) {
            //把释放的内存放到releaseMemory中去
            *(void**)p = ReleaseMemory;
            ReleaseMemory = p;
        }
    }
protected:
    void* ReleaseMemory = NULL; //一开始releasemem是空的
    //为什么是静态成员变量?只在本文件可见!
    static size_t _size; //单个内存对象的大小
    size_t _maxSize;//最大的block的内存大小
    MemBlock* RequestMemory;
private:
};
size_t MemPool::_size = 16; //单个内存对象的大小设置为8bytes
​
​
int main() {
    MemPool memPool;
    void* p = memPool.allocate();
    printf("p:malloc: %p\n", p);
    void* p2 = memPool.allocate();
    printf("p2:malloc: %p\n", p2);
    memPool.deallocate(p);
    printf("p:free: %p\n", p);
​
    void* p3 = memPool.allocate();
    printf("p3:malloc: %p\n", p3);
​
    memPool.deallocate(p2);
    printf("p2:free: %p\n", p2);
​
    void* p4 = memPool.allocate();
    printf("p4:malloc: %p\n", p4);
​
    memPool.deallocate(p3);
    memPool.deallocate(p4);
​
    return 0;
}
​
//运行结果
​
调用MemPool构造函数
p:malloc: 0x121e05de0
p2:malloc: 0x121e05df0
p:free: 0x121e05de0
p3:malloc: 0x0
p2:free: 0x121e05df0
p4:malloc: 0x0
调用mempool析构函数~()


评论