Table of Contents

原生内存与对象池使用指南(AlignedMemoryManager / NativeArena / NodeArena / PinnedBufferPool / OverflowPool)

给谁看:需要非托管内存/池化的组件开发者(引擎热路径、对齐缓冲、批量临时区)。 回答什么:五种内存积木怎么选、怎么借怎么还、什么不要做。 ⚠️ 铁律(先记住):非托管内存 GC 不管——自己 new 的要 Dispose,池里借的要归还, 两种都不做 = 真泄漏。完整积木全景见 ../COORDINATION.md


0. 选型(需求 → 用哪个)

需求 线程安全 一句话
单块对齐原生内存(O_DIRECT/DMA/16B CAS 背板) AlignedMemoryManager 否(单块单线程读写) new(size, alignment),挂到对象即用,Dispose 释放
短生命周期 bump 分配(批量/临时缓冲) NativeArena 否(单线程,建议 ThreadStatic) 一次分配大块,线性 bump,Reset 复用
长命变长节点驻留(并发分配、指针恒稳) NodeArena Alloc 是(并发安全)/ Dispose 归结构层收口 4MB 块 CAS-bump,只增不减,无需逐节点释放
pinned/对齐 buffer 池(高频 Rent/Return) PinnedBufferPool 是(全局池多线程安全) 分桶 + thread-local 栈,热路径零分配
轻量固定容量对象池(非内存对象) OverflowPool<T> 是(多线程安全) ConcurrentQueue,满则 disposer 回收

1. 借还规则(最容易错的地方)

来源 归还方式 说错就错
自己 new AlignedMemoryManager(...) Dispose() 不 Dispose = 真泄漏(无 finalizer)
pool.Rent(size)byte[] pool.Return(buf) 别 Dispose——归还即复用
pool.RentAligned(...)AlignedMemoryManager pool.ReturnAligned(mem) 不是 Dispose(池 Reset 复用);带归属池校验,别交叉归还

原文曾有"RentAligned 产物必须 Dispose"的说法——那是错的:池产物归还是 ReturnAligned, Dispose 会绕过池的复用协议。借了要还、建了要 Dispose,两条路都不可省。


2. AlignedMemoryManager —— 单块对齐原生内存

using var mem = new AlignedMemoryManager(size: 4096, alignment: AlignmentConst.Alignment4K);

Span<byte> span = mem.GetSpan();                    // 含校验(对外接口用)
ref Header h  = ref mem.GetRef<Header>(offset: 0);  // 强类型引用
Span<byte> fast = mem.GetSpanUnsafe(0, 64);         // 热路径零校验(调用方自证合法)
unsafe { byte* p = mem.BytePtr; }                   // 原生指针(O_DIRECT pread/pwrite)
  • 继承 MemoryManager<byte>——可给 ReadAsync(Memory<byte>)
  • ⚠️ lockPhysicalMemory=true:Linux 需 CAP_IPC_LOCK 或调高 RLIMIT_MEMLOCKalignment 须 ≥ 系统页大小。
  • ⚠️ 无 finalizer——不 Dispose 即真泄漏。

3. NativeArena —— 线性 bump 分配

using var arena = new NativeArena(size: 1 << 20);   // 一次分配整块
Span<int> nums = arena.Allocate<int>(count: 1024);  // 线性 bump(只前进,无单块 Free)
arena.Reset();                                      // offset 归零复用(内存不释放)
  • 适合批量/临时缓冲;多线程并发 Allocate 须外部同步。
  • 有 finalizer 兜底但不保证时效——仍应主动 Dispose。

4. NodeArena —— 并发安全的变长节点驻留竞技场

using var arena = new NodeArena();            // 4MB 块起步(ChunkSize = 1 << 22)

Node* node;
unsafe { node = (Node*)arena.Alloc(sizeof(Node)); }   // 8 对齐分配,返回恒稳裸指针
// 填充节点字段后把指针缓存进上层映射表——跨块增长长命有效
  • 指针恒稳:块只增不减、永不重分配搬移——缓存进上层结构的裸节点指针在 arena 持续增长时 长命有效(SkipList 节点驻留形态的基座)。
  • 并发 Alloc 安全:4MB 块内 Interlocked bump(无锁快路径,读者侧 admit 也安全); 块满建新块(lock 双检)。
  • 无逐节点释放:块内存生命周期 = arena 生命周期(Dispose 全释放)——配合"节点缓存不逐出" 的索引形态;并发竞争的重复分配 = 有界字节浪费,非泄漏。
  • 单写者 + 并发读者契约:Alloc 可并发;Dispose 由结构层 Resources 收口(确定性先于读者终结)。
  • NativeArena 分野:NativeArena = 单线程线性 bump + Reset 复用(批量临时缓冲); NodeArena = 并发分配 + 只增不减 + 指针恒稳(长命驻留对象)。

5. PinnedBufferPool —— pinned/对齐 buffer 池(旗舰池)

using var pool = new PinnedBufferPool(maxPerBucket: 64);

// pinned byte[] 池
byte[] buf = pool.Rent(size: 4097, zeroMemory: false);   // size 向上取整 2 的幂(4097→8192)
try { /* 用 buf */ }
finally { pool.Return(buf); }                            // 归还(不是 Dispose)

// 对齐原生内存池
AlignedMemoryManager mem = pool.RentAligned(size: 4096, alignment: AlignmentConst.Alignment4K);
try { Span<byte> s = mem.GetSpan(); /* 用 */ }
finally { pool.ReturnAligned(mem); }                     // 归还(池 Reset 复用,非 Dispose)
  • size 向上取整到 2 的幂(最多 50% padding)——别指望精确大小。
  • 别和 ArrayPool<byte>.Shared 混用——归还非本池 buffer 会被静默忽略/Dispose。
  • ReturnAligned池身份校验:归还非本池产出的会被拒——别交叉归还。
  • pool.Dispose() 释放所有 buffer(全局栈 + 各 thread-local 栈)。

6. OverflowPool<T> —— 轻量固定容量对象池

using var pool = new OverflowPool<MyContext>(size: 32, disposer: ctx => ctx.Cleanup());

if (pool.TryGet(out MyContext? ctx))   // 借:命中 true / 池空 false(自行 new)
    ctx.Reuse();
else
    ctx = new MyContext();
pool.TryAdd(ctx);                      // 还:未满入池 true / 满→disposer 回收 false
  • 容量是软约束(高并发瞬时可能超几个,无正确性影响)。
  • disposer 默认 no-op——对象若持有非托管资源必须传回收回调,否则池满丢弃即泄漏。

7. 决策速查

我要单块对齐内存(O_DIRECT / CAS 背板)?
  → new AlignedMemoryManager(size, alignment)。using / Resources。热路径 GetSpanUnsafe。

我要批量临时缓冲(用完整体丢)?
  → new NativeArena(size)。Allocate/AllocateBytes,用完 Reset 复用或 Dispose。

我要长命驻留的变长节点(并发分配、指针永不搬移)?
  → new NodeArena()。Alloc(size) 得恒稳裸指针;不逐节点释放,Dispose 由结构层收口。

我要高频 Rent/Return 复用 buffer?
  → PinnedBufferPool。Rent/Return(pinned 数组)或 RentAligned/ReturnAligned(对齐原生内存)。
    size 取整 2 幂;别跨池归还;Dispose 释放全部。

我要复用非内存对象(会话/上下文)?
  → OverflowPool<T>(size, disposer)。TryGet/TryAdd;满则 disposer 回收。