源自 Monibuca 流媒体服务器

一棵树,
管住每一个字节

GoMem 是为 Go 打造的高性能内存分配器。它用单棵 Treap同时维护空闲块的 地址顺序与大小顺序:分配约 12ns,释放自动合并相邻块,节点对象池化复用, 零 GC 压力。

观看分配/回收动画
  • ~12ns单次分配
  • 0allocs/op
  • 408×mmap 创建提速
  • -99.98%初始内存占用
How it works

看懂这棵树:分配与回收的全过程

下面的动画由 allocator.go 的逐行 JS 移植驱动,每一次比较、旋转、摘除、合并都会被真实地播放出来。

01

一棵树,两种秩序

每个空闲块是一个节点。横向按 Start 构成二叉搜索树(中序 = 地址顺序),便于找相邻块;纵向按 size 通过旋转维持堆序,小块上浮。

02

分配:找 → 摘 → 切 → 插

find 沿树寻找够大的块并尽量向左找更贴合的;选中后旋转下沉成叶子再摘除,切下头部 size 字节,剩余部分重新插入。

03

回收:找邻居 → 合并 → 插

按地址在树中查找 End == offset 的左邻与 Start == offset+size 的右邻,能合并就合并,被吞并的节点回到对象池,避免 GC。

访问路径 候选块 选中/删除 旋转 插入/合并结果 相邻块
内存池 [0, 1024) 空闲(树中节点) 已分配(点击释放)
02565127681024

已分配的内存块

暂无

操作记录

    Features

    为高吞吐场景而生

    Treap

    多种分配策略

    默认单棵 Treap,分配快 77–86%;也可用 twotree 标签切换为双 AVL 树,Find 快一倍。

    Buddy

    伙伴分配器

    通过 enable_buddy 启用 Buddy 系统做内存池化,大块内存按 2 的幂高效复用。

    RM

    可回收内存

    RecyclableMemory 批量分配、一次性回收,比关闭时快 53%,内存占用更低。

    Scale

    可扩展分配器

    ScalableMemoryAllocator 空间不足时自动扩容子分配器,Borrow 仅需 ~5ns。

    Reader

    零拷贝 Reader

    多缓冲区读取器,跨 buffer 读取、Skip、Range、LEB128 解码均无需拷贝。

    mmap

    mmap + 透明大页

    enable_mmap 惰性占用物理内存;Linux 上自动开启 THP,显著降低 TLB miss。

    Benchmarks

    数字说话

    Apple M2 Pro (ARM64) · Go 1.23.0

    单树 vs 双树:分配耗时 ns/op,越短越好

    Basic
    12.33
    22.71
    64B
    12.32
    22.60
    8KB
    12.14
    22.61
    Random
    12.47
    23.02
    单树 Treap(默认)双树 AVL

    MMAP:分配器创建 16MB

    317.2µs默认 · 16,777,405 B
    →
    777nsmmap · 216 B
    408×创建更快
    59.8%整体更快 (geomean)
    86.6%内存减少 (geomean)

    ScalableMemoryAllocator

    5.4nsBorrow
    13.2nsMalloc
    23.3nsFreeRest
    0 B每次操作堆分配
    Get started

    三行代码上手

    package main
    
    import "github.com/langhuihui/gomem"
    
    func main() {
        // 创建可扩展分配器
        allocator := gomem.NewScalableMemoryAllocator(1024)
    
        buf := allocator.Malloc(256)            // 分配
        copy(buf, []byte("Hello, World!"))      // 使用
        allocator.Free(buf)                     // 释放(自动与相邻空闲块合并)
    }
    allocator := gomem.NewScalableMemoryAllocator(1024)
    buf := allocator.Malloc(1024)
    
    part1 := buf[0:256]
    part2 := buf[256:512]
    part3 := buf[512:1024]
    
    allocator.Free(part1)   // 可以只释放其中一段
    allocator.Free(part2)   // 与 part1 合并成 [0, 512)
    copy(part3, []byte("still usable"))
    allocator.Free(part3)   // 全部归还,树重新回到一个节点
    allocator := gomem.NewScalableMemoryAllocator(1024)
    rm := gomem.NewRecyclableMemory(allocator)
    
    buf1 := rm.NextN(128)
    buf2 := rm.NextN(256)
    copy(buf1, []byte("Buffer 1"))
    copy(buf2, []byte("Buffer 2"))
    
    rm.Recycle()            // 一次性回收全部内存
    # 双 AVL 树实现(Find 更快)
    go build -tags=twotree
    
    # 启用伙伴分配器做内存池化
    go build -tags=enable_buddy
    
    # 关闭可回收内存特性
    go build -tags=disable_rm
    
    # mmap 分配(Linux 自动启用透明大页 THP)
    go build -tags=enable_mmap
    并发提示:同一个分配器的 Malloc 与 Free 应在同一个 goroutine 中调用。 推荐搭配 gotask:在 Start 中分配、在 Dispose 中释放。