在高并发内存池的设计中,"大页内存管理"和"元数据开销优化"是两个核心痛点:原生 malloc/free 在大内存分配时频繁触发系统调用,而 new/delete 管理内存池元对象(如 ThreadCache、span)会引入额外性能损耗。本文基于 TCMalloc 思想,拆解高并发内存池中大页内存的申请/释放逻辑,以及如何通过**定长内存池(ObjectPool)**彻底脱离 new/delete,实现元数据的零开销管理。
一、背景:为什么要单独处理大页内存?
在高并发内存池的三级缓存架构(ThreadCache→CentralCache→PageCache)中,我们将内存分为"小对象(≤256KB)"和"大对象(>256KB)":
- 小对象:走三级缓存,利用线程私有、桶锁、批量分配降低竞争
- 大对象:若仍走缓存,会导致"缓存污染"(大内存占满缓存,小对象无空间可用),且大内存分配频率低,缓存收益有限
因此,大对象直接走 PageCache 申请连续物理页(大页),跳过 ThreadCache 和 CentralCache,核心目标是:
- 减少系统调用次数(批量申请连续页,而非单次小内存)
- 避免大内存碎片化(用 span 管理连续页块,空闲后自动合并)
- 保证高并发下的线程安全(全局页锁 + 细粒度控制)
二、核心 1:大页内存的申请与释放实现
2.1 大页内存的定义与申请策略
(1)大页判定规则
我们定义"大对象"为超过 256KB 的内存(可根据场景调整),对应物理页数量为:
// Common.h 核心宏定义
#define MAX_BYTES (256 * 1024) // 小对象上限
#define PAGE_SIZE 8192 // 单页 8KB
#define PAGE_SHIFT 13 // 2^13=8192,用于页 ID 与地址转换
#define MAX_PAGE_BUCKETS 128 // 最大连续页数量(128*8KB=1024KB)
typedef size_t PAGE_ID; // 页 ID 类型
当申请内存 size > MAX_BYTES 时,判定为大对象,直接走 PageCache 申请连续页(大页),而非三级缓存。
(2)大页内存申请核心逻辑
大页申请的核心是"以页为单位分配连续物理页",避免原生 malloc 的碎片化问题,核心代码如下:
// ConcurrentAlloc.h - 用户层大对象申请接口
void* ConcurrentAlloc(size_t size) {
if (size > MAX_BYTES) {
// 1. 内存对齐:大对象对齐到页大小,避免跨页浪费
size_t alignSize = SizeClass::RoundUp(size);
kPages = alignSize >> PAGE_SHIFT;
PageCache::()->_pageMutex.();
span* bigSpan = PageCache::()->(kPages);
PageCache::()->_pageMutex.();
addr = ()bigSpan->_pageId * PAGE_SIZE;
(*)addr;
}
}

