高并发内存池(16)-三层缓存的回收过程
高并发内存池(16)-三层缓存的回收过程
内存池的回收过程是内存管理系统的关键环节,它通过分层协作和智能合并机制,确保内存高效重复利用。以下是完整的回收流程解析:
一、回收触发场景
- ThreadCache回收:线程释放小对象(≤256KB)
- CentralCache回收:ThreadCache批量返还或Span完全空闲
- PageCache回收:CentralCache返还完整Span
二、三级回收流程
1. ThreadCache层级(无锁)
// ThreadCache释放单个对象void Deallocate(void* ptr, size_t size) { FreeList* list = GetFreeList(size); list->Push(ptr); // 头插法插入自由链表 // 如果自由链表过长,批量返还CentralCache if (list->Size() >= list->MaxSize()) { void* start, *end; size_t batchNum = list->PopRange(start, end, list->MaxSize()/2); CentralCache::ReleaseListToSpans(start, size); }}
特点:
- 线程本地操作无锁竞争
- 批量返还减少锁开销(如每次返还50%的对象)
- 自由链表采用头插法(O(1)时间复杂度)
2. CentralCache层级(桶锁)
// CentralCache接收ThreadCache返还的内存块void ReleaseListToSpans(void* start, size_t size) { size_t index = SizeClass::Index(size); _spanLists[index]._mtx.lock(); // 加桶锁 while (start) { void* next = NextObj(start); Span* span = PageCache::MapObjectToSpan(start); // 找到所属Span // 头插法归还到Span的自由链表 NextObj(start) = span->_freeList; span->_freeList = start; span->_useCount--; // 如果Span完全空闲,返还PageCache if (span->_useCount == 0) { _spanLists[index].Erase(span); PageCache::ReleaseSpanToPageCache(span); // 触发合并 } start = next; } _spanLists[index]._mtx.unlock();}
关键点:
- 桶锁粒度:不同size的SpanList有独立锁,减少竞争
- 引用计数:通过
_useCount
判断Span是否完全空闲 - 批量处理:一次处理多个对象,减少锁持有时间
3. PageCache层级(全局锁)
// PageCache合并空闲Spanvoid ReleaseSpanToPageCache(Span* span) { std::lock_guard lock(_pageMtx); // 向前合并(检查前一页是否空闲) while (MergePrevSpan(span)) {} // 向后合并(检查后一页是否空闲) while (MergeNextSpan(span)) {} // 插入对应大小的SpanList _spanLists[span->_n].PushFront(span); // 更新页号映射(首尾页都指向该Span) _idSpanMap[span->_pageId] = span; _idSpanMap[span->_pageId + span->_n - 1] = span;}
合并算法:
bool MergePrevSpan(Span* span) { PAGE_ID prevId = span->_pageId - 1; auto it = _idSpanMap.find(prevId); if (it == _idSpanMap.end()) return false; Span* prevSpan = it->second; if (prevSpan->_isUse || prevSpan->_n + span->_n > 128) return false; // 执行合并 span->_pageId = prevSpan->_pageId; span->_n += prevSpan->_n; _spanLists[prevSpan->_n].Erase(prevSpan); delete prevSpan; return true;}
合并效果:
[已用][空闲SpanA][空闲SpanB][已用] 合并后→ [已用][大空闲Span][已用]
三、回收过程示意图
sequenceDiagram participant ThreadCache participant CentralCache participant PageCache ThreadCache->>CentralCache: 批量返还对象(带桶锁) CentralCache->>Span: 更新自由链表和引用计数 alt Span完全空闲 CentralCache->>PageCache: 返还Span(全局锁) PageCache->>PageCache: 尝试前后页合并 PageCache->>空闲链表: 插入合并后的Span end
四、设计精髓
-
分层缓冲:
- ThreadCache:线程独占,无锁
- CentralCache:共享但分桶加锁
- PageCache:全局锁但操作频率低
-
合并策略:
- 立即合并:发现相邻空闲Span立即合并
- 双向检查:同时检查前后页,最大化合并机会
-
映射维护:
// 示例:合并后更新映射_idSpanMap[newSpan->_pageId] = newSpan; // 首页_idSpanMap[newSpan->_pageId + newSpan->_n - 1] = newSpan; // 尾页
- 确保通过任意页都能找到所属Span
-
锁粒度优化:
- ThreadCache:完全无锁
- CentralCache:按size分桶加锁
- PageCache:全局锁但操作较少
五、性能关键点
- 批量操作:
- ThreadCache攒够一批对象才返还CentralCache
- CentralCache批量操作Span的自由链表
- 合并触发时机:
- 只在Span完全空闲时尝试合并
- 避免频繁合并带来的开销
- 热点分离:
- 小对象高频操作走ThreadCache
- 大对象低频操作走PageCache
六、异常处理
- 跨线程释放:
- 对象必须由分配它的线程释放(TLS机制保证)
- 否则需要特殊处理(如加入全局释放队列)
- 内存泄漏检测:
- 通过
_useCount
判断未释放的内存 - 定期扫描
_idSpanMap
检查泄漏
- 通过
通过这种分层回收设计,内存池实现了:
- 高频小对象分配/释放:无锁操作,性能接近O(1)
- 大对象管理:系统级分配,避免碎片
- 自适应合并:智能平衡内存利用率与合并开销