频繁调用 malloc/free 会导致内存碎片化,影响程序性能。内存池(Memory Pool)通过预分配大块内存并在池内部分配,显著减少系统调用碎片化问题。本文详细介绍内存池的原理与实现。
为什么需要内存池
标准的 malloc/free 存在以下问题:
- 系统调用开销:每次分配都可能触发 brk 或 mmap 系统调用
- 内存碎片:频繁分配释放后,内存空间呈碎片化
- 缓存失效:可能导致 CPU 缓存命中率下降
适用场景
内存池适合需要频繁分配释放固定大小对象的场景,如网络服务器、数据库连接池、对象池等。
简单固定块内存池
首先实现一个固定大小的内存池,每次分配固定大小的内存块:
#include
#include
#include
#define POOL_BLOCK_SIZE 4096 /* 每块 4KB */
#define BLOCK_SIZE 64 /* 每个对象 64 字节 */
typedef struct Block {
struct Block *next;
char data[1]; /* 可变长度数据 */
} Block;
typedef struct MemPool {
Block *used_list;
Block *free_list;
size_t block_size;
} MemPool;
void *pool_init(size_t size) {
MemPool *pool = malloc(sizeof(MemPool));
if (!pool) return NULL;
pool->block_size = size;
pool->free_list = NULL;
pool->used_list = NULL;
/* 预分配初始块 */
for (int i = 0; i 16; i++) {
void *ptr = pool_alloc(pool);
if (ptr) pool_free(pool, ptr);
}
return pool;
}
void *pool_alloc(MemPool *pool) {
if (!pool->free_list) {
/* 分配新块 */
Block *block = malloc(sizeof(Block) + pool->block_size - 1);
if (!block) return NULL;
/* 添加到使用列表 */
block->next = pool->used_list;
pool->used_list = block;
return block->data;
}
/* 从空闲列表取出 */
Block *block = pool->free_list;
pool->free_list = block->next;
/* 添加到使用列表 */
block->next = pool->used_list;
pool->used_list = block;
return block->data;
}
void pool_free(MemPool *pool, void *ptr) {
if (!ptr) return;
/* 找到对应的 Block */
Block *block = (char *)ptr - offsetof(Block, data);
/* 从使用列表移除 */
Block **prev = &pool->used_list;
while (*prev && *prev != block) prev = &(*prev)->next;
if (!*prev) return;
*prev = block->next;
/* 添加到空闲列表 */
block->next = pool->free_list;
pool->free_list = block;
}
slab 分配器
slab 分配器为不同大小的对象维护不同的缓存池,进一步提高内存利用效率:
#include
#define SLAB_SIZE 4096
#define MAX_SLABS 32
typedef struct Slab {
void *memory;
size_t object_size;
size_t count;
size_t free_count;
void **free_list;
struct Slab *next;
} Slab;
typedef struct SlabAllocator {
Slab *slabs[MAX_SLABS];
size_t object_size;
} SlabAllocator;
void *slab_alloc(SlabAllocator *alloc) {
for (Slab **s = alloc->slabs; *s; s = &(*s)->next) {
if ((*s)->free_count > 0) {
void *ptr = (*s)->free_list;
(*s)->free_list = (void *)*(*s)->free_list;
(*s)->free_count--;
return ptr;
}
}
/* 创建新的 slab */
Slab *slab = malloc(sizeof(Slab));
slab->memory = malloc(SLAB_SIZE);
slab->object_size = alloc->object_size;
slab->count = SLAB_SIZE / alloc->object_size;
slab->free_count = slab->count;
/* 初始化空闲链表 */
slab->free_list = malloc(sizeof(void *) * slab->count);
char *p = slab->memory;
for (size_t i = 0; i < slab->count - 1; i++) {
slab->free_list[i] = p;
p += alloc->object_size;
}
slab->free_list[slab->count - 1] = NULL;
slab->next = alloc->slabs[0];
alloc->slabs[0] = slab;
return slab->memory;
}
TLSF 分配器
TLSF(Two Level Segregate Fit)是一个高效的内存分配器,适合实时系统:
/* TLSF 核心思路:两级位图快速定位空闲块 */
/*
* 第一级:将内存按 2^r 大小分类(如 32, 64, 128...)
* 第二级:将每类再细分多个小块
* 使用位图快速查找第一个非空列表
*/
#define FLI 6 /* 第一级数量:2^6 = 64 ~ 2^31 */
#define SLI 4 /* 第二级每级 4 个块 */
typedef struct TLSF {
void *heap;
unsigned int fl_bitmap; /* 第一级位图 */
unsigned int sl_bitmap[FLI]; /* 第二级位图 */
void *free_list[FLI][SLI]; /* 空闲块链表 */
} TLSF;
/* 快速找到第一个非空块 */
int ffs(unsigned int x) {
if (!x) return -1;
int i = 0;
while (!(x & 1)) { x >>= 1; i++; }
return i;
}
Arena 内存分配器
Arena 分配器一次性分配大块内存,所有分配共享同一个生命周期,适合临时对象:
typedef struct Arena {
char *base;
size_t offset;
size_t capacity;
} Arena;
Arena *arena_create(size_t capacity) {
Arena *arena = malloc(sizeof(Arena));
arena->base = malloc(capacity);
arena->offset = 0;
arena->capacity = capacity;
return arena;
}
void *arena_alloc(Arena *arena, size_t size) {
/* 对齐到 8 字节 */
size_t align = (size + 7) & ~7;
if (arena->offset + align > arena->capacity) return NULL;
void *ptr = arena->base + arena->offset;
arena->offset += align;
return ptr;
}
void arena_reset(Arena *arena) {
arena->offset = 0; /* 快速重置,所有内存可复用 */
}
void arena_destroy(Arena *arena) {
free(arena->base);
free(arena);
}
Arena 适用场景
解析 JSON/XML、编译器的符号表构建、短期存在的临时数据等。分配的对象通常一起释放,不需要单独释放。
总结
- 固定块内存池:适合对象大小固定的场景,实现简单
- slab 分配器:支持多种大小,Linux 内核使用此方案
- TLSF:O(1) 分配时间,适合实时系统
- Arena:批量分配和释放,适合临时对象
根据具体场景选择合适的内存池实现,可以显著提升程序性能和内存利用效率。