内存分配器性能优化

背景

在之前我们提到采用自定义的内存分配器来解决防止频繁 make 导致的 gc 问题。gc 问题本质上是 CPU 消耗,而内存分配器本身如果产生了大量的 CPU 消耗那就得不偿失。经过测试初代内存分配器实现过于简单,产生了很多 CPU 消耗,因此必须优化内存分配器的性能。

性能消耗原因

在内存的分配和回收上,使用了简单的循环检测,当内存碎片较多的时候,循环消耗非常可观

查找可分配的内存

找到回收的内存偏移

性能优化

很快在社区中大家给出了一个称为 Buddy 的内存分片算法,那么这个算法是否能解决问题呢?

Buddy 算法

这是一个非常高效的算法,采用的是满二叉树数据结构,用一个数组来表示,然而当实际使用时却遇到了问题,因为我需要在自研的 BufReader 中使用,因此不能出现内存缝隙。Buffdy 算法在回收内存时只能按照申请什么回收什么的原则。举例,我申请了一个var a []byte = alloc(100),那么回收必须也是回收 free(a)。而自研的 BufReader,需要"部分回收"能力。比如先回收a[50:],然后再回收a[:50]。那么 Buddy 算法将无能为力。

当然,这个算法最终还会用到,这里先留个悬念。

双圣树模型

这是我自己起的名字,实际上是利用两颗平衡二叉树来实现快速找到可分配的内存以及快速回收内存。

go 复制代码
type	Allocator struct {
		pool       []*Block
		sizeTree   *Block
		offsetTree *Block
		Size       int
		// history    []History
	}

分配树

这颗树,用来快速查找可分配的内存,我们将可分配的内存用一个节点表示

go 复制代码
type	Block struct {
		Start, End int
		trees      [2]Tree
	}

sizeTree 通过对每个节点的大小(End-Start)进行排序,在分配时,通过查找树中刚好大于等于待分配大小的节点,再修改这个节点,对树进行平衡即可。

回收树

这颗树,用来找到回收内存块前后的 Block,通过合并或者插入 Block 达到回收内存的效果。

合并前面 合并后面 合并前后 仅插入

共享节点

由于两棵树只是表达了不同的排序,里面所有节点的数量和属性都是相同的,因此不需要两套节点,只需要公用一套节点集合即可。

go 复制代码
type Tree struct {
		left, right *Block
		height      int
	}

每个节点有两套指针,分别指向两棵树的不同的子节点,从而在逻辑上形成了两棵树。

进阶优化

虽然我们最终通过双圣树模型,实现了内存分配器的性能优化,但是优化并没有因此而停止。因为上述的内存分配器是无锁的,只适合给单个 goroutine 使用,如果加锁则性能大打折扣。

那么从宏观角度来说,分配器持有的大内存块也会存在需要回收的情况。比如在流销毁的时候。

再次使用 Buddy 算法

这时候大内存块就不需要部分回收了,此时就又可以采用 Buddy 算法了。我们只需要在申请大内存块时,按照 2 倍数来申请,可以最大化利用。最终我们形成了两级内存分配。当然在这里就需要用锁了。

相关推荐
xingyuzhisuan26 分钟前
Blender渲染加速:4090服务器OptiX后端性能提升50%
运维·服务器·性能优化·gpu算力
小羊子说1 小时前
Android 音频系统深度解析:从 App 到内核的完整链路
android·人工智能·性能优化·车载系统
之歆2 小时前
前端性能优化:从路由懒加载到打包优化
前端·性能优化
木斯佳3 小时前
前端八股文面经大全:快手前端一面 (2026-04-07)·面经深度解析
前端·ai·性能优化·hooks·移动端适配
猿小喵3 小时前
MySQL长时间未提交事务分析
数据库·mysql·性能优化
Rick19934 小时前
Java 接口高并发优化方案
java·性能优化·高并发
有想法的py工程师4 小时前
PostgreSQL 性能优化实战:一条 Order by 的 SQL 从 5 秒优化到 100ms
大数据·数据库·postgresql·性能优化
猿小喵1 天前
MySQL慢查询分析与处理-第二篇
数据库·mysql·性能优化
LilySesy1 天前
【与AI+】英语day4——数据库与性能优化
数据库·oracle·性能优化·sap·abap·自动翻译
切糕师学AI1 天前
深入浅出顺序磁盘 I/O:原理、优化与应用
性能优化·i/o·磁盘