您现在的位置是:首页 > 文章详情

GoMem - 高性能内存分配器库

日期:2025-10-11点击:22

GoMem 是一个为 Go 语言设计的高性能内存分配器库,从 Monibuca 项目中提取而来。

特性

  • 多种分配策略: 支持单树和双树(AVL)分配算法
  • 伙伴分配器: 可选的伙伴系统,用于高效的内存池管理
  • 可回收内存: 支持内存回收,具有自动清理功能
  • 可扩展分配器: 动态增长的内存分配器
  • 内存读取器: 高效的多缓冲区读取器,支持零拷贝操作

构建标签

该库支持多个构建标签来自定义行为:

  • twotree: 使用双树(AVL)实现替代单树 treap
  • enable_buddy: 启用伙伴分配器进行内存池管理
  • disable_rm: 禁用可回收内存功能以减少开销

安装

go get github.com/langhuihui/gomem

使用方法

基本内存分配

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)
}

分段内存释放

package main

import "github.com/langhuihui/gomem"

func main() {
    // 创建一个可扩展的内存分配器
    allocator := gomem.NewScalableMemoryAllocator(1024)
    
    // 分配一大块内存
    buf := allocator.Malloc(1024)
    
    // 使用内存的不同部分
    part1 := buf[0:256]    // 前256字节
    part2 := buf[256:512]  // 中间256字节  
    part3 := buf[512:1024] // 后512字节
    
    // 填充数据
    copy(part1, []byte("Part 1 data"))
    copy(part2, []byte("Part 2 data"))
    copy(part3, []byte("Part 3 data"))
    
    // 分段释放内存 - 可以释放部分内存
    allocator.Free(part1)  // 释放前256字节
    allocator.Free(part2)  // 释放中间256字节
    
    // 继续使用剩余内存
    copy(part3, []byte("Updated part 3"))
    
    // 最后释放剩余内存
    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()

内存缓冲区操作

// 创建一个内存缓冲区
mem := gomem.NewMemory([]byte{1, 2, 3, 4, 5})

// 添加更多数据
mem.PushOne([]byte{6, 7, 8})

// 获取总大小和缓冲区数量
fmt.Printf("Size: %d, Buffers: %d\n", mem.Size, mem.Count())

// 转换为字节数组
data := mem.ToBytes()

内存读取器

// 创建一个内存读取器
reader := gomem.NewReadableBuffersFromBytes([]byte{1, 2, 3}, []byte{4, 5, 6})

// 读取数据
buf := make([]byte, 6)
n, err := reader.Read(buf)
// buf 现在包含 [1, 2, 3, 4, 5, 6]

并发安全

⚠️ 重要: Malloc 和 Free 操作必须在同一个协程中调用,以避免竞态问题。为了更优雅的使用,建议使用 gotask,可以在 Start 方法中申请内存,在 Dispose 方法中释放内存。

// ❌ 错误:不同的协程
go func() {
    buf := allocator.Malloc(256)
    // ... 使用缓冲区
}()

go func() {
    allocator.Free(buf) // 竞态条件!
}()

// ✅ 正确:同一个协程
buf := allocator.Malloc(256)
// ... 使用缓冲区
allocator.Free(buf)

// ✅ 优雅:使用 gotask
type MyTask struct {
    allocator *gomem.ScalableMemoryAllocator
    buffer []byte
}

func (t *MyTask) Start() {
    t.allocator = gomem.NewScalableMemoryAllocator(1024)
    t.buffer = t.allocator.Malloc(256)
}

func (t *MyTask) Dispose() {
    t.allocator.Free(t.buffer)
}

性能考虑

  • 在高吞吐量场景中使用 enable_buddy 构建标签以获得更好的内存池性能
  • 启用 RecyclableMemory 比禁用版本快53%,且内存使用更少
  • 仅在不需要内存管理功能时使用 disable_rm 构建标签(减少复杂度但牺牲性能)
  • 单树分配器比双树分配器显著更快(分配操作快77-86%)
  • 仅在需要更快查找操作时使用 twotree 构建标签(比单树快100%)

基准测试结果

以下基准测试结果在 Apple M2 Pro (ARM64) 和 Go 1.23.0 环境下获得:

单树 vs 双树分配器性能比较

操作类型 单树 (ns/op) 双树 (ns/op) 性能差异 胜出者
基础分配 12.33 22.71 快84% 单树
小内存分配 (64B) 12.32 22.60 快84% 单树
大内存分配 (8KB) 12.14 22.61 快86% 单树
顺序分配 1961 3467 快77% 单树
随机分配 12.47 23.02 快85% 单树
查找操作 3.03 1.51 快100% 双树
获取空闲大小 3.94 4.27 快8% 单树

关键发现:

  • 单树分配器在内存分配操作上快77-86%
  • 双树分配器仅在查找操作上快100%
  • 由于分配性能更优,推荐在大多数用例中使用单树分配器

RecyclableMemory 性能比较(启用 vs 禁用)

操作类型 启用 RM (ns/op) 禁用 RM (ns/op) 性能差异 内存使用
基础操作 335.2 511.9 快53% 启用: 1536B/2 allocs, 禁用: 1788B/2 allocs
多个分配 - 1035.1 - 禁用: 3875B/10 allocs
Clone操作 - 53.7 - 禁用: 240B/1 alloc

关键发现:

  • 启用 RecyclableMemory 在基础操作上快53%
  • 启用 RM 内存使用更少(1536B vs 1788B 基础操作)
  • 启用 RM 提供真正的内存管理和回收功能
  • 禁用 RM 使用简单的 make([]byte, size) 无内存池

内存分配器性能(单树)

基准测试 操作次数/秒 每次操作时间 内存/操作 分配次数/操作
Allocate 96,758,520 15.08 ns 0 B 0
AllocateSmall 98,864,434 12.49 ns 0 B 0
AllocateLarge 100,000,000 12.65 ns 0 B 0
SequentialAlloc 1,321,965 942.2 ns 0 B 0
RandomAlloc 96,241,566 12.79 ns 0 B 0
GetFreeSize 303,367,089 3.934 ns 0 B 0

内存操作性能

基准测试 操作次数/秒 每次操作时间 内存/操作 分配次数/操作
PushOne 31,982,593 35.05 ns 143 B 0
Push 17,666,751 70.40 ns 259 B 0
ToBytes 119,496 11,806 ns 106,496 B 1
CopyTo 417,379 2,905 ns 0 B 0
Append 979,598 1,859 ns 7,319 B 0
Count 1,000,000,000 0.3209 ns 0 B 0
Range 32,809,593 36.08 ns 0 B 0

内存读取器性能

基准测试 操作次数/秒 每次操作时间 内存/操作 分配次数/操作
Read 10,355,643 112.4 ns 112 B 2
ReadByte 536,228 2,235 ns 56 B 2
ReadBytes 2,556,602 608.7 ns 1,080 B 18
ReadBE 408,663 3,587 ns 56 B 2
Skip 8,762,934 125.8 ns 56 B 2
Range 15,608,808 70.99 ns 80 B 2
RangeN 20,101,638 79.09 ns 80 B 2
LEB128Unmarshal 356,560 3,052 ns 56 B 2

伙伴分配器性能

基准测试 操作次数/秒 每次操作时间 内存/操作 分配次数/操作
Alloc 4,017,826 388.2 ns 0 B 0
AllocSmall 3,092,535 410.7 ns 0 B 0
AllocLarge 3,723,950 276.4 ns 0 B 0
SequentialAlloc 62,786 17,997 ns 0 B 0
RandomAlloc 3,249,220 357.8 ns 0 B 0
Pool 27,800 56,846 ns 196,139 B 0
NonPowerOf2 3,167,425 317.8 ns 0 B 0

性能总结

  • 单树分配器: 极快的分配/释放操作,每次操作约12ns,零内存分配
  • 双树分配器: 分配较慢(约23ns每次操作),但查找操作更快(约1.5ns vs 3ns)
  • 启用 RecyclableMemory: 比禁用版本快53%,内存效率更高
  • 禁用 RecyclableMemory: 实现更简单但性能较慢,内存使用更高
  • 内存操作: 高效的缓冲区管理,开销最小
  • 内存读取器: 高性能读取,支持零拷贝操作
  • 伙伴分配器: 快速的2的幂次分配,支持池化以减少GC压力

推荐:

  • 由于分配性能更优,推荐在大多数应用中使用单树分配器(默认)
  • 保持 RecyclableMemory 启用(默认)以获得更好的性能和内存效率
  • 仅在查找操作关键且频繁时才使用双树分配器
  • 仅在不需要内存管理功能时才使用 disable_rm 标签
原文链接:https://www.oschina.net/p/gomem
关注公众号

低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。

持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。

转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。

文章评论

共有0条评论来说两句吧...

文章二维码

扫描即可查看该文章

点击排行

推荐阅读

最新文章