内存池

内存池是什么?

内存池是一种提前申请好一块大内存,再从中按需划分固定/可变大小的内存块进行复用的技术。
内存的申请和释放操作不再频繁调用 malloc/freenew/delete,而是从池中“取”或“还”。

使用内存池的原因:

  1. 减少系统调用开销,提高性能;

    系统的malloc/free需要陷入内核态,代价比较大,高频申请和释放内存会频繁触发系统调用,而使用内存池可以减少系统调用次数;

  2. 避免内存碎片

    多次分配/释放不同大小的内存容易导致堆内存碎片,因为多次分配小内存,在堆中可能会导致大量的内存空洞,明明有足够的内存,但因为内存空洞导致分配失败,即使是可以进行内存压缩,这部分性能损耗也是不可忽视的,使用内存池可以采用统一的块大小分配,虽然可能会造成一部分浪费,但是以少量的内存浪费来换取更高的性能是合理的,如果根据业务场景进行针对性优化,可以将这部分浪费降到最低。

  3. 提高内存分配速度

    内存池中分配块内存可以做到O(1),因为在内存池分配内存是一个简单的取和还操作,可以使用链表或者 bitmap进行管理,效率远高于系统调用。

  4. 易于资源回收与生命周期管理

  5. 更适合多线程环境的高并发访问

​ 内存池可以设计成线程本地内存池;也可以设计成无锁队列;

  1. 可用于自定义内存调试 / 统计 / 追踪

比如统计每类对象的分配数量、峰值;

检测未释放的指针、重复释放;

定位内存泄漏。

常见适用场景

场景 描述
网络服务(如 DPDK、nginx) 每个连接、请求都要分配多个结构体
游戏开发 对象重复使用、对象池
数据库引擎 缓冲页、查询计划缓存
嵌入式/实时系统 必须快速且可控的内存行为
协议栈开发 包结构、状态管理,需频繁分配/释放

内存碎片的内碎片和外碎片

内碎片是指已经分配了的内存卡块的未使用部分,通常是padding产生的;

外碎片是系统有足够的空闲内存,但是由于不连续导致无法分配。

一个简单的内存池实现:

结构体

typedef 
struct mempool_s {
	int blocksize;
	int freecount;
	char *free_ptr;
	char *mem;
} mempool_t; 

创建内存池接口

int memp_create(mempool_t *m, int block_size) {
	if (!m) return -1;
	m->blocksize = block_size;
	m->freecount = MEM_PAGE_SIZE / block_size;
	m->mem = (char *)malloc(MEM_PAGE_SIZE); 
	if (!m->mem) {  //NULL
		return -2;
	}
	memset(m->mem, 0, MEM_PAGE_SIZE); 
	m->free_ptr = m->mem;
	int i = 0;
	char *ptr = m->mem;
	for (i = 0;i < m->freecount;i ++) {
		*(char **)ptr = ptr + block_size;
		ptr = ptr + block_size;
	} 
	*(char **)ptr = NULL;
	return 0;
}

分配内存

void *memp_alloc(mempool_t *m) {
	if (!m || m->freecount == 0) return NULL;
	void *ptr = m->free_ptr;
	m->free_ptr = *(char **)ptr;
	m->freecount --;
	return ptr;
}

释放内存

void memp_free(mempool_t *m, void *ptr) {
	*(char **)ptr = m->free_ptr;
	m->free_ptr = (char *)ptr;
	m->freecount ++;
}

销毁内存池

void memp_destory(mempool_t *m) {
	if (!m) return;
	if (m->mem) {
		free(m->mem);
		m->mem = NULL;
	}
	m->free_ptr = NULL;
	m->blocksize = 0;
	m->freecount = 0;
}

使用C++的,维护多个内存块大小的内存池实现

namespace Mempool
{

#define MEMORY_POOL_NUM     64 
#define SLOT_BASE_SIZE      8
#define MAX_SLOT_SIZE       512

struct Slot{
    std::atomic<Slot*> next;
};

class MemoryPool{
private:
    int BlockSize_; //内存块大小
    int SlotSize_;  //槽大小
    Slot* firstBlock_;  //指向内存池管理的首个实际的内存块;
    Slot* curSlot_;     //指向当前未被使用的槽
    std::atomic<Slot*>  freeList_;  //指向空闲的槽(使用过后又释放的槽)
    Slot* lastSlot_;    //当前内存块中最后能存放元素的位置标志,超过需要申请内存卡
    // std::mutex mutexForFreeList_; //保证freeList_在多线程中操作的原子性
    std::mutex mutexForBlock_;      //保证多线程环境下不必要的重复开辟内存导致的浪费
public:
    MemoryPool(size_t BlockSize = 4096);
    ~MemoryPool();
public:
    void init(size_t);
    void* allocate();
    void deallocateNewBlock(void*);
private:
    void allocateNewBlock();
    size_t padPointer(char* p,size_t align);
    bool pushFreeList(Slot* slot);
    Slot* popFreeList();

};

class HashBucket{
public:
    static void initMemoryPool();
    static MemoryPool& getMemoryPool(int index);
    static void* useMemory(size_t size){
        if(size <= 0) return nullptr;
        if(size > MAX_SLOT_SIZE) return operator new(size);
        return getMemoryPool(((size + 7) / SLOT_BASE_SIZE) - 1).allocate();
    }

    static void freeMemory(void* ptr,size_t size){
        if(!ptr) return ;
        if(size > MAX_SLOT_SIZE){
            operator delete(ptr);
            return;
        }
        getMemoryPool(((size + 7) / SLOT_BASE_SIZE) - 1).deallocateNewBlock(ptr);
    }

    template<typename T,typename... Args>
    friend T* newElement(Args&&... args);

    template<typename T>
    friend void deleteElement(T* p);
};

template<typename T,typename... Args>
T* newElement(Args&&... args){
    
}


template<typename T>
void deleteElement(T* p){
    if(p){
        p->~T();
        HashBucket::freeMemory(reinterpret_cast<void*>(p),sizeof(T));
    }
}

}
posted @ 2025-04-02 19:33  Tohomson  阅读(62)  评论(0)    收藏  举报