从零实现高并发内存池:TCMalloc 核心架构拆解

多线程程序中频繁 malloc/free 的锁竞争可能吞噬大量性能。本文拆解一个仿 Google TCMalloc 的高并发内存池,看如何用"每线程缓存 + 三级架构"解决这个问题。


核心文件清单

文件 作用
Common.h 公共头文件:SizeClass、FreeList、Span、SpanList、系统内存接口
ObjectPool.h 定长内存池(Span 对象的专用内存池)
ThreadCache.h/cpp 线程局部缓存
CentralCache.h/cpp 中心缓存
PageMap.h 基数树,页号到 Span 的 O(1) 映射
PageCache.h/cpp 页缓存
conCurrentAlloc.h 对外统一分配/释放接口

代码详见gitee

一、概述

为什么需要内存池?

问题 说明
malloc 库函数,多线程下需加锁,竞争激烈时性能急剧下降
系统调用慢 mmap/brk 每次陷入内核
内存碎片 频繁申请释放小对象会导致大量碎片,降低缓存命中率

性能表现

指标 malloc 本内存池
多线程吞吐 慢(全局锁竞争) 快(TLS 无锁分配)
小对象分配 极快(TLS 中直接 Pop)
大对象分配 一般 直接 PageCache 或系统申请

二、整体架构

整个内存池分三层,每一层解决特定粒度的问题:

复制代码
┌──────────────────────────────────────────────────────┐
│                  ThreadCache (TLS)                    │
│  每个线程独立,无锁访问,管理 208 个自由链表桶           │
└──────────────────┬───────────────────────────────────┘
                   │ 批量取 / 批量还
                   ▼
┌──────────────────────────────────────────────────────┐
│                  CentralCache (单例)                   │
│  所有线程共享,桶锁粒度,管理 208 个 Span 双向链表       │
└──────────────────┬───────────────────────────────────┘
                   │ 申请 Span / 回收 Span
                   ▼
┌──────────────────────────────────────────────────────┐
│                   PageCache (单例)                     │
│  管理 129 个页链表,负责 Span 的切分与前后向合并         │
│  直接与操作系统(mmap/posix_memalign)交互              │
└──────────────────┬───────────────────────────────────┘
                   │ 系统申请 / 释放
                   ▼
┌──────────────────────────────────────────────────────┐
│                    System (mmap)                      │
└──────────────────────────────────────────────────────┘

ThreadCache:

Central Cache:

Page Cache:

分配流程速览

复制代码
ConCurrentAlloc(16)
    │
    ├── size > 256KB? ──→ PageCache::NewSpan() ──→ 系统申请 → 直接返回
    │
    └── size <= 256KB?
        │
        ├── ThreadCache 对应桶不为空? ──→ 直接 Pop 返回 ✅(最快)
        │
        └── 桶为空?
            │
            └── FetchFromCentralCache()
                │
                ├── 慢启动计算 batchNum
                ├── CentralCache::FetchRangeObj()
                │   ├── 对应 SpanList 无空闲 Span?
                │   │   └── GetOneSpan()
                │   │       ├── 当前桶有 Span? → 取出来切分
                │   │       └── 没有? → PageCache::NewSpan()
                │   │           ├── 对应页桶有? → 取出
                │   │           ├── 后面大桶有? → 切分大 Span
                │   │           └── 都没有? → 系统申请 128 页
                │   └── 从 Span 的 freeList 取 batchNum 个对象
                │
                └── 1 个直接返回,其余 PushRange 到 ThreadCache

释放流程速览

复制代码
ConCurrentFree(ptr)
    │
    ├── 查页表找 ptr 所属 Span → 获取 _objSize
    │
    ├── size > 256KB? ──→ PageCache::ReleaseSpanToPageCache()
    │   │                    ├── 前后页合并(非使用中且不超 128 页)
    │   │                    └── 合并后挂回对应页链表
    │
    └── size <= 256KB?
        │
        └── ThreadCache::Deallocate()
            ├── Push 回对应桶
            └── 桶太长了? → ListTooLong() → 还一批给 CentralCache
                └── CentralCache::ReleaseListToSpans()
                    ├── 根据页表找到所属 Span
                    ├── 头插回 Span 的 freeList
                    └── _userCount == 0? → 整个 Span 还给 PageCache 合并

三、核心组件详解

1. SizeClass --- 大小映射与对齐

将用户请求的任意字节对齐到固定粒度,映射到 208 个桶之一。将对齐后相同大小的对象放在同一个桶中,减少桶的数量,控制内碎片在 10% 以内

对齐规则

区间 对齐粒度 桶范围
[1, 128] 8 字节 [0, 16)
[129, 1024] 16 字节 [16, 72)
[1025, 8K] 128 字节 [72, 128)
[8K+1, 64K] 1024 字节 [128, 184)
[64K+1, 256K] 8K 字节 [184, 208)

核心函数

cpp 复制代码
// 向上对齐到 alignNum 的整数倍,纯位运算
static inline size_t _RoundUp(size_t bytes, size_t alignNum) {
    return (bytes + alignNum - 1) & ~(alignNum - 1);
}

// 桶索引:计算 size 映射到 freelist[208] 的哪个下标
static inline size_t _Index(size_t bytes, size_t alignShift) {
    return ((bytes + (1 << alignShift) - 1) >> alignShift) - 1;
}

_RoundUp 位运算拆解(以 8 字节对齐为例):

复制代码
bytes = 5, alignNum = 8
(5 + 7) & ~7 = 12 & 0xF8 = 8 // 5 → 8
(8 + 7) & ~7 = 15 & 0xF8 = 8 // 8 → 8
(9 + 7) & ~7 = 16 & 0xF8 = 16 // 9 → 16

批量大小计算

cpp 复制代码
// 一次从 CentralCache 批量取多少个对象
// 小对象批量多(最多 512),大对象批量少(最少 2)
static size_t NumMoveSize(size_t size) {
    int num = MAX_BYTES / size;  // 256K / size
    if (num < 2) num = 2;
    if (num > 512) num = 512;
    return num;
}

// 这批对象需要占用多少页
static size_t NumMovePage(size_t size) {
    size_t num = NumMoveSize(size);
    size_t npage = num * size;
    npage >>= PAGE_SHIFT;  // 转为页数
    if (npage == 0) npage = 1;
    return npage;
}

NumMoveSize 的大小依赖:小对象(如 16 字节)批量上限 512,大对象(如 128KB)批量上限 2。这保证了一次批量操作的总字节数不会超过 256KB。


2. FreeList --- 自由链表

管理切分好的同大小小块内存,本质是一个单向链表

关键设计 :内存块的前 sizeof(void*) 字节被复用为 next 指针,不额外分配节点内存。

cpp 复制代码
static void*& NextObj(void* obj) {
    return *(void**)obj;
}

核心接口

操作 说明
Push(obj) 头插一个对象
PushRange(start, end, n) 头插一段范围(批量归还)
Pop() 头删一个对象
PopRange(start, end, n) 头删一段范围(批量取出)
MaxSize() 慢启动上限
Size() 当前节点数

为什么用 LIFO? 刚刚释放的内存块大概率还在 CPU 缓存中,优先使用能提高缓存命中率。


3. Span --- 内存跨度管理

Span 是内存管理的核心元数据,描述一段连续的内存页。

cpp 复制代码
struct Span {
    PAGE_ID _pageId = 0;   // 起始页号
    size_t _n = 0;          // 页数
    Span* _next = nullptr;  // 双向链表指针
    Span* _prev = nullptr;
    size_t _userCount = 0;  // 已分配给 ThreadCache 的小对象计数
    void* _freeList = nullptr; // 切好的小块内存链表
    bool _isUse = false;    // 是否在使用中
    size_t _objSize = 0;    // 该 Span 中每个对象的大小
};

关键字段

  • _userCount:记录从该 Span 切出了多少个小对象。释放回来一个就减 1,归零时说明所有小块都回来了,整个 Span 可以回收
  • _freeList:Span 从 PageCache 获得时是一整块连续内存,切分成小对象后通过这个指针组织成链表
  • _objSize:记录 Span 里切的是多大的对象,释放时用于判断归还层级

4. SpanList --- Span 双向链表

管理 Span 的双向循环链表,带桶锁。

cpp 复制代码
class SpanList {
    Span* _head;             // 哨兵头节点
    std::mutex _mtx;         // 桶锁
};

每桶一把锁,线程操作不同桶时互不干扰,大幅降低锁冲突概率。


5. ObjectPool --- 定长内存池

专门为 Span 对象本身提供的内存池,避免用 new 反复申请元数据造成开销。

cpp 复制代码
template<class T>
class ObjectPool {
    char* _memory = nullptr;       // 大块内存指针
    size_t _remainBytes = 0;       // 剩余可用字节
    void* _freeList = nullptr;     // 回收的对象复用链表
};

分配策略

  1. 先看 _freeList 有没有回收的对象,有则复用
  2. 没有则从 _memory 大块上切,不够就 malloc(128KB) 新大块

释放策略

  1. 调用 obj->~T() 析构函数
  2. 头插到 _freeList 等待复用
cpp 复制代码
T* New() {
    // 路径一:有回收对象 → 复用
    if (_freeList) {
        void* next = *(void**)_freeList;
        obj = (T*)_freeList;
        _freeList = next;
    }
    // 路径二:从大块上切
    else {
        if (_remainBytes < sizeof(T)) {
            _memory = (char*)malloc(128 * 1024);
            _remainBytes = 128 * 1024;
        }
        obj = (T*)_memory;
        size_t objSize = sizeof(T) < sizeof(void*) ? sizeof(void*) : sizeof(T);
        _memory += objSize;
        _remainBytes -= objSize;
    }
    new(obj) T;  // placement new 调构造函数
    return obj;
}

void Delete(T* obj) {
    obj->~T();                            // 先析构
    *(void**)obj = _freeList;             // 头插复用链表
    _freeList = obj;
}

6. ThreadCache --- 线程缓存

每个线程独立拥有的缓存,无锁分配,是性能关键。

cpp 复制代码
static thread_local ThreadCache* pTLSThreadCache = nullptr;

每个线程首次访问时,系统在 TLS 区域为该线程单独分配一个 ThreadCache*。名字相同,但物理内存完全隔离,操作自己的 ThreadCache 完全不需要加锁。

分配流程

cpp 复制代码
void* ThreadCache::Allocate(size_t size) {
    size_t alignSize = SizeClass::RoundUp(size);
    size_t index = SizeClass::Index(size);
    if (!_freeLists[index].Empty())
        return _freeLists[index].Pop();    // 快路径:无锁
    return FetchFromCentralCache(index, alignSize);
}

释放流程

cpp 复制代码
void ThreadCache::Deallocate(void* ptr, size_t size) {
    size_t index = SizeClass::Index(size);
    _freeLists[index].Push(ptr);
    // 桶里攒太多了,释放一批回 CentralCache
    if (_freeLists[index].Size() >= _freeLists[index].MaxSize())
        ListTooLong(_freeLists[index], size);
}

何时触发归还? 空闲对象数量 >= MaxSize() 时,归还 MaxSize() 个,防止某线程囤积过多空闲内存。


7. 慢启动批量获取算法

初始时不要一次拿太多,用多少拿多少,按需增长。

cpp 复制代码
size_t batchNum = std::min(_freeLists[index].MaxSize(),
                            SizeClass::NumMoveSize(size));
if (_freeLists[index].MaxSize() == batchNum)
    _freeLists[index].MaxSize() += 1;  // 慢增长

流程

  1. _maxSize 初始为 1,每次从 CentralCache 取 1 个
  2. 再次用完时 _maxSize += 1,逐渐增大批量
  3. 上限不超过 NumMoveSize(size)

为什么? 用量少时取太多浪费;取太多可能把 CentralCache 的 Span 搬空。自适应:用得多就多取,用得少就少取。


8. CentralCache --- 中心缓存

全局共享的中心缓存,线程们在这里批量取/还内存,用桶锁保护并发。

单例模式

cpp 复制代码
class CentralCache {
    static CentralCache _sInst;
    static CentralCache* GetInstance() { return &_sInst; }
    SpanList _spanLists[NFREELIST];  // 208 个桶
};

FetchRangeObj --- 从 CentralCache 取一批对象

cpp 复制代码
size_t CentralCache::FetchRangeObj(void*& start, void*& end,
                                    size_t batchNum, size_t size) {
    Span* span = GetOneSpan(_spanLists[index], size);
    start = span->_freeList;
    end = start;
    size_t actualNum = 1;
    while (actualNum < batchNum - 1 && NextObj(end) != nullptr) {
        end = NextObj(end);
        ++actualNum;
    }
    span->_freeList = NextObj(end);
    NextObj(end) = nullptr;
    span->_userCount += actualNum;
    return actualNum;
}

GetOneSpan --- 要么取现有 Span,要么创建新 Span

cpp 复制代码
Span* CentralCache::GetOneSpan(SpanList& list, size_t size) {
    // 1. 遍历当前桶,找有空闲对象的 Span
    Span* it = list.Begin();
    while (it != list.End()) {
        if (it->_freeList != nullptr) return it;
        it = it->_next;
    }
    // 2. 没有 → 去 PageCache 申请新 Span
    Span* span = PageCache::GetInstance()->NewSpan(NumMovePage(size));
    span->_objSize = size;
    // 3. 把 Span 的大块内存切分成小对象,串成链表
    char* start = (char*)(span->_pageId << PAGE_SHIFT);
    char* end = start + (span->_n << PAGE_SHIFT);
    span->_freeList = start;
    start += size;
    void* tail = span->_freeList;
    while (start < end) {
        NextObj(tail) = start;
        tail = NextObj(tail);
        start += size;
    }
    NextObj(tail) = nullptr;
    list.PushFront(span);
    return span;
}

ReleaseListToSpans --- 归还一批对象

cpp 复制代码
void CentralCache::ReleaseListToSpans(void* start, size_t size) {
    while (start) {
        void* next = NextObj(start);
        Span* span = PageCache::GetInstance()->MapObjectToSpan(start);
        NextObj(start) = span->_freeList;
        span->_freeList = start;
        span->_userCount--;
        if (span->_userCount == 0) {
            // Span 完全空闲,还给 PageCache 合并
            _spanLists[index].Erase(span);
            span->_freeList = nullptr;
            PageCache::GetInstance()->ReleaseSpanToPageCache(span);
        }
        start = next;
    }
}

9. PageCache --- 页缓存

管理物理页,负责 Span 的切分合并。同样使用单例模式,管理 129 个桶,第 k 个桶管理大小为 k 页的 Span。

cpp 复制代码
class PageCache {
    SpanList _pageLists[NPAGES];       // 129 个桶
    ObjectPool<Span> _spanPool;        // Span 对象的定长内存池
    PageMap _idSpanMap;                // 页号 → Span 映射
    std::mutex _mtx;                   // 全局锁
};

为什么用全局锁而不是桶锁? 合并操作需要跨桶操作(前后相邻页面可能在不同桶中),桶锁无法保证。但 PageCache 访问频率远低于 CentralCache,锁竞争不激烈。

NewSpan --- 获取 k 页的 Span

cpp 复制代码
Span* PageCache::NewSpan(size_t k) {
    // 1. >128 页 → 直接向系统申请
    if (k > NPAGES - 1) { /* SystemAlloc + 建映射 */ }

    // 2. 当前桶有 → 直接取
    if (!_pageLists[k].Empty()) return _pageLists[k].PopFront();

    // 3. 找后面大桶,切分
    for (int i = k + 1; i < NPAGES; i++) {
        if (!_pageLists[i].Empty()) {
            Span* nspan = _pageLists[i].PopFront();   // 取大 Span
            Span* kspan = _spanPool.New();             // 新建小 Span
            kspan->_pageId = nspan->_pageId;           // 头部切出 k 页
            kspan->_n = k;
            nspan->_pageId += k;                       // 剩余部分
            nspan->_n -= k;
            _pageLists[nspan->_n].PushFront(nspan);
            // 建立映射(剩余部分首尾两页 + kspan 所有页)
            _idSpanMap.set(nspan->_pageId, nspan);
            _idSpanMap.set(nspan->_pageId + nspan->_n - 1, nspan);
            for (PAGE_ID i = 0; i < kspan->_n; i++)
                _idSpanMap.set(kspan->_pageId + i, kspan);
            return kspan;
        }
    }
    // 4. 都没有 → 系统申请 128 页大 Span,递归调用自己
    Span* bigSpan = _spanPool.New();
    void* ptr = SystemAlloc(NPAGES - 1);
    bigSpan->_pageId = (PAGE_ID)ptr >> PAGE_SHIFT;
    bigSpan->_n = NPAGES - 1;
    _pageLists[bigSpan->_n].PushFront(bigSpan);
    return NewSpan(k);  // 递归,第二次就能找到对应桶
}

ReleaseSpanToPageCache --- 回收 Span 并尝试合并

cpp 复制代码
void PageCache::ReleaseSpanToPageCache(Span* span) {
    // >128 页不合并,直接还给系统
    if (span->_n > NPAGES) { SystemFree(ptr, span->_n); return; }

    // 向前合并
    while (1) {
        PAGE_ID prevId = span->_pageId - 1;
        Span* prev = (Span*)_idSpanMap.get(prevId);
        if (!prev || prev->_isUse) break;
        if (prev->_n + span->_n > NPAGES - 1) break;
        span->_pageId = prev->_pageId;
        span->_n += prev->_n;
        _pageLists[prev->_n].Erase(prev);
        _spanPool.Delete(prev);
    }
    // 向后合并(对称逻辑)

    // 重建整段映射 --- ★ 不然后续查映射读到野指针
    _pageLists[span->_n].PushFront(span);
    span->_isUse = false;
    for (PAGE_ID i = span->_pageId; i < span->_pageId + span->_n; i++)
        _idSpanMap.set(i, span);
}

合并条件

  1. 邻居页号映射存在
  2. 邻居 Span 不在使用中
  3. 合并后不超过 128 页

为什么释放时必须重建整段映射? 合并前旧 Span 的中间页映射仍指向已回收的对象,不重建的话后续 MapObjectToSpan 返回野指针,读 _objSize 时会段错误。


10. PageMap --- 基数树

替代 std::unordered_map 实现页号到 Span 指针的快速映射。项目使用 PageMap3<36>

特性 std::unordered_map 基数树
时间复杂度 O(1) 均摊,最坏 O(n) O(1) 严格
哈希冲突
缓存友好
内存占用 固定较大 按需分配

三种变体:

变体 层级 BITS=36 时的可行性
PageMap1 1 级 需要 512GB 连续内存,不可能
PageMap2 2 级 叶子 16GB,仍不现实
PageMap3 3 级 每节点 32KB,按需分配,可行

PageMap3 分层结构(BITS=36):

cpp 复制代码
INTERIOR_BITS = (36 + 2) / 3 = 12  // 每层 4096 项
LEAF_BITS = 36 - 2 * 12 = 12       // 叶子 4096 项

地址解码

cpp 复制代码
PAGE_ID id = addr >> PAGE_SHIFT;  // 36 bit 页号
i1 = id >> (12 + 12);             // 高 12 位 → 根节点索引
i2 = (id >> 12) & 0xFFF;          // 中 12 位 → 内节点索引
i3 = id & 0xFFF;                  // 低 12 位 → 叶子节点索引

节点按需分配:未访问的页号范围不消耗内存。


四、辅助组件

SystemAlloc / SystemFree

跨平台封装系统级内存申请/释放:

  • Linuxposix_memalign(&ptr, 4096, size) --- 4KB 对齐
  • WindowsVirtualAlloc(0, size, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE)
  • 释放:Linux 用 free(),Windows 用 VirtualFree()

ConCurrentAlloc / ConCurrentFree

对外统一分配/释放接口:

cpp 复制代码
static void* ConCurrentAlloc(size_t size) {
    if (size > MAX_BYTES) {  // 大对象 > 256KB
        // 直接走 PageCache(或系统),不经过前两层缓存
        Span* span = PageCache::GetInstance()->NewSpan(kpage);
        return (void*)(span->_pageId << PAGE_SHIFT);
    }
    // 小对象:走 ThreadCache
    if (pTLSThreadCache == nullptr)
        pTLSThreadCache = new ThreadCache;
    return pTLSThreadCache->Allocate(size);
}

static void ConCurrentFree(void* ptr) {
    Span* span = PageCache::GetInstance()->MapObjectToSpan(ptr);
    size_t size = span->_objSize;
    if (size > MAX_BYTES)   // 大对象直接还给 PageCache
        PageCache::GetInstance()->ReleaseSpanToPageCache(span);
    else                    // 小对象还给 ThreadCache
        pTLSThreadCache->Deallocate(ptr, size);
}

五、数据流全景

复制代码
malloc(16) ──→ ConCurrentAlloc(16)
                  │
                  ├─ RoundUp(16) = 16, Index(16) = 1
                  │
                  ├─ ThreadCache 桶[1] 有? → Pop() ✅
                  │
                  └─ 没有?
                       ├─ MaxSize=1, NumMoveSize(16)=512
                       ├─ batchNum = min(1,512) = 1
                       ├─ MaxSize() += 1 → 2
                       │
                       └─ CentralCache::FetchRangeObj(batchNum=1)
                            ├─ _spanLists[1] 有 Span?
                            │   ├─ 有 → 从其 freeList 取 1 个
                            │   └─ 没有 → GetOneSpan()
                            │        ├─ PageCache::NewSpan(1)
                            │        │   ├─ _pageLists[1] 有? → 直接取
                            │        │   ├─ 没有 → 找 _pageLists[2..128]
                            │        │   │   → 找到 _pageLists[4] 有 4 页 Span
                            │        │   │   → 切出 1 页给新 Span
                            │        │   │   → 剩下 3 页挂 _pageLists[3]
                            │        │   └─ 后面都没? → 系统申请 128 页
                            │        │       → 挂 _pageLists[128]
                            │        │       → 递归 NewSpan(1) 重新找
                            │        │
                            │        └─ 把 Span 切成 16 字节对象链表
                            │
                            └─ 返回 1 个对象给 ThreadCache ✅


free(ptr) ──→ ConCurrentFree(ptr)
                  │
                  ├─ MapObjectToSpan(ptr) → 查页表得 Span
                  ├─ 得 _objSize = 16
                  │
                  ├─ ThreadCache 桶[1] Push(ptr)
                  │
                  └─ Size() >= MaxSize()?
                       └─ 是 → ListTooLong()
                            ├─ PopRange() 取一批
                            └─ CentralCache::ReleaseListToSpans()
                                 ├─ MapObjectToSpan 找 Span
                                 ├─ 头插回 Span 的 freeList
                                 └─ _userCount == 0?
                                      └─ 是 → 整个 Span 还给 PageCache
                                           ├─ 向前向后合并空闲 Span
                                           └─ 挂回对应页桶

六、常见面试问题

Q1:为什么用三层架构?两层不行吗?

如果只有 ThreadCache + 系统堆:ThreadCache 用完只能找系统,系统调用代价大,也无法线程间借调。如果只有 CentralCache + 系统堆:所有线程竞争桶锁,没有快路径。

三层架构的巧妙之处

  • ThreadCache:最频繁的操作完全无锁
  • CentralCache:线程间内存调度,桶锁减小竞争
  • PageCache:底层页管理,切分与合并对抗碎片

Q2:thread_local 关键字的作用?

每个线程独立拥有一个 ThreadCache* 变量,互不干扰。完全避免了锁的开销------这是速度优势的核心来源。首次使用时创建,线程结束时系统自动回收。

Q3:Span 中的 _userCount 有什么作用?

引用计数,记录从该 Span 切出了多少小对象。_userCount == 0 时意味着此 Span 的所有小块都归还了,可以还给 PageCache 尝试合并。

为什么不能只看 freeList? 当前 Span 的 freeList 可能为空,但 ThreadCache 还持有该 Span 的部分对象。必须用 _userCount 才能真正确定。

Q4:内碎片和外碎片是什么?

类型 说明 解决方法
内碎片 分配比实际需要大 SizeClass 对齐控制在 10% 以内
外碎片 已分配内存之间的空洞 PageCache 的 Span 合并机制

Q5:什么是慢启动算法?

控制 ThreadCache 每次从 CentralCache 批量获取的数量:初始 1,每次 Fetch 时增长,上限不超过 NumMoveSize(size)。用多少取多少,用得少不多拿。

Q6:MapObjectToSpan 如何工作?

地址右移 13 位得到页号,通过基数树找到对应的 Span 对象。不同于 unordered_map,基数树通过位运算直接算下标,O(1) 且无哈希冲突。

Q7:为什么选 PageMap3<36> 而不是 PageMap1PageMap2

BITS=36 时,PageMap1 需要 512GB 连续数组,PageMap2 叶子节点 16GB,都不现实。PageMap3 每节点仅 32KB,按需分配,总开销与使用量成正比。

Q8:大对象(>256KB)的分配释放有何不同?

不经过 ThreadCache 和 CentralCache,直接由 PageCache 分配(或系统直接分配)。大对象操作频率低,走缓存意义不大,反而影响小对象的缓存效率。

Q9:这个项目的瓶颈在哪里?如何改进?

瓶颈 说明 可能的改进
PageCache 全局锁 所有 CentralCache 没 Span 时都等这把锁 分段锁、无锁数据结构
内存占用 ThreadCache 可能缓存大量空闲内存 更激进的归还策略、自适应水位线
跨平台 posix_memalign 部分系统性能不如 mmap 按平台优化底层内存申请

相关推荐
Thomas_Lee_OR1 小时前
多Agent路径规划 LaCAM for multi-agent path finding (MAPF)
算法·路径规划·仓储机器人·mapf
nvd111 小时前
深度解析:Kong Hybrid 模式与 KIC (Gateway API) 架构演进与核心异同
架构·gateway·kong
一切皆是因缘际会1 小时前
可落地数字生命工程:从记忆厮杀到自我意识觉醒全链路,AGI内生智能硅基生命心智建模
人工智能·深度学习·算法·机器学习·ai·系统架构·agi
nlpming2 小时前
opencode Agent 详解
算法
江南十四行2 小时前
排序算法进阶:直接插入排序(简单排序)与希尔排序
数据结构·算法·排序算法
nlpming2 小时前
opencode System Prompt 构建机制 & AGENTS.md注入机制
算法
nlpming2 小时前
opencode - 安装和配置
算法
nlpming2 小时前
opencode 内置工具
算法
nlpming2 小时前
opencode - 常用命令&自定义命令
算法