Memory Pool

SGI Memory Pool

大于 128Bytes 直接使用 malloc / free ,只管理内存,不负责构造与析构。

小于 128Bytes 使用自定义内存空间管理。

  • 8、16、24、... 、128 大小的内存块指针数组统一管理
1
2
3
4
union _Obj {
union _Obj* _M_free_list_link;
char _M_client_data[1]; /* The client sees this. */
};
  • 每个大小内存块组织在一个大的内存空间,维护 起始地址、结束地址和对空间大小
1
2
3
static char* _S_start_free;
static char* _S_end_free;
static size_t _S_heap_size;
  • 组织可用内存块为一个链表,顶层管理的指针数组中的元素,指向对应内存块区域中,首个可用的内存块地址
  • 申请和释放内存块,就是在改变链表的指向

当然,已经申请的内存,在内存池回收前,都不会释放。

内存池管理接口:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 分配内存的入口函数
static void* allocate(size_t __n)

// 把分配好的chunk块添加到自由链表当中
static void* _S_refill(size_t __n);

// 分配相应__size字节大小的chunk块,__nobjs个(函数内可修改)
static char* _S_chunk_alloc(size_t __size, int& __nobjs);

// 把chunk块归还到内存池
static void deallocate(void* __p, size_t __n);

// 内存池扩容函数
template <bool threads, int inst>
void*
__default_alloc_template<threads, inst>::reallocate(void* __p,
size_t __old_sz,
size_t __new_sz);

另外又两个辅助函数:

地址对齐用的函数 _S_round_up

1
2
3
4
5
/*将 __bytes 上调至最邻近的 8 的倍数*/
static size_t _S_round_up(size_t __bytes) {
return (((__bytes) + (size_t) _ALIGN-1) & ~((size_t) _ALIGN - 1));
}
// ~((size_t) _ALIGN - 1) 得到一个二进制掩码 1111...1000

内存块指针数组中找到对应 chunk 块大小的函数 _S_freelist_index

1
2
3
4
/*返回 __bytes 大小的chunk块位于 free-list 中的编号*/
static size_t _S_freelist_index(size_t __bytes) {
return (((__bytes) + (size_t)_ALIGN-1)/(size_t)_ALIGN - 1);
}

NGINX Memory Pool

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
struct ngx_pool_s {
ngx_pool_data_t d; //内存池数据管理相关指针的结构体
size_t max; //ngx_pool_data_t可分配的最大内存值,超过此值则使用 ngx_pool_large_t 分配内存
ngx_pool_t *current; //当前内存池指针
ngx_chain_t *chain; //挂接一个ngx_chain_t结构
ngx_pool_large_t *large; //大块内存链表
ngx_pool_cleanup_t *cleanup; //释放内存池的操作集(callback函数)
ngx_log_t *log; //日志信息
};

typedef struct ngx_pool_s ngx_pool_t;

typedef struct {
u_char *last; //当前内存池分配到的末尾地址,即下一次分配开始地址
u_char *end; //内存池结束位置
ngx_pool_t *next; //下一块内存
ngx_uint_t failed; //当前块内存分配失败次数
} ngx_pool_data_t;

小块内存,通过 ngx_pool_data_t 组织为链表结构,在 last 到 end 之间分配内存使用。不足时,会新开辟一个内存块。

1
2
3
4
struct ngx_pool_large_s {
ngx_pool_large_t *next; // 下一个大块内存
void *alloc; // 记录分配的大块内存的起始地址
};

大块内存,通过 ngx_pool_large_s 组织为链表,使用和释放就是包装之后的 malloc 和 free。

1
2
3
4
5
6
7
8
9
10
typedef void (*ngx_pool_cleanup_pt)(void *data); // 清理回调函数的类型定义

typedef struct ngx_pool_cleanup_s ngx_pool_cleanup_t;

// 清理操作的类型定义,包括一个清理回调函数,传给回调函数的数据和下一个清理操作的地址
struct ngx_pool_cleanup_s {
ngx_pool_cleanup_pt handler; // 清理回调函数
void *data; // 传递给回调函数的指针
ngx_pool_cleanup_t *next; // 指向下一个清理操作
};

内存池的清理,一般在内存池销毁之前。

内存池的一般接口为:

1
2
3
4
5
6
7
8
9
10
11
ngx_pool_t *ngx_create_pool(size_t size, ngx_log_t *log); // 创建内存池
void ngx_destroy_pool(ngx_pool_t *pool); // 销毁内存池
void ngx_reset_pool(ngx_pool_t *pool); // 重置内存池

void *ngx_palloc(ngx_pool_t *pool, size_t size); // 内存分配函数,支持内存对齐
void *ngx_pnalloc(ngx_pool_t *pool, size_t size); // 内存分配函数,不支持内存对齐
void *ngx_pcalloc(ngx_pool_t *pool, size_t size); // 内存分配函数,支持内存初始化0

ngx_int_t ngx_pfree(ngx_pool_t *pool, void *p); // 内存释放(大块内存),不支持小块内存

ngx_pool_cleanup_t *ngx_pool_cleanup_add(ngx_pool_t *p, size_t size); // 添加清理handler

小块内存池的申请,需要手动进行内存对齐,以增加内存操作的效率,因为没有使用 malloc 不会自动对齐。

1
2
#define ngx_align_ptr(p, a)                                       \
(u_char *) (((uintptr_t) (p) + ((uintptr_t) a - 1)) & ~((uintptr_t) a - 1))

这个操作的逻辑和 _S_round_up 是一致的。将指针 p 向上对齐到最接近的 a 的倍数。

实现与测试

github link ;一个类似的简化版 NGINX 内存池实现 MemoryPool

整体而言,自定义实现的 MemoryPool allocator 性能比 SGI Memory Pool 性能会好一些。 SGI Memory Pool 的性能和 STL 默认 allocator 性能相差无几。NGINX Memory Pool 性能会比 SGI Memory Pool 更好。但是NGINX Memory Pool 并没有实现为 allocator 的形式。

内存管理相关

对 malloc 的进一步优化:

jemalloc

mimalloc well document

tcmalloc

其中 jemalloc 和 mimallic 会是更优的选择。

相关学习博客推荐为:

内存分配器之jemalloc技术原理分析

JeMalloc

看了但是忘了,还是随便总结一点可能会好一些吧。


本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!