SagittaEngine 内存管理系统

C++ 自定义内存管理系统,本质上来说,是由管理器从系统申请一块大的内存,程序其他部分的内存申请都从管理器的这块大内存中获得,而不直接向系统获取。这样做有几个好处。

1 避免频繁向系统申请内存,提高内存分配效率,特别是对于申请频率很高的小对象
2 建立自己的小对象内存管理器,能有效减少内存碎片
3 由于所有的内存分配都是自己管理的,便于进行内存泄漏等的检查

sagitta engine c++ custom memory allocator

SagittaEngine 中实现了一套内存管理系统,整体结构如下。

Sagitta Engine 内存管理系统结构

一个内存申请发出后,分配器首先判断内存的大小,如果大于 256 字节的,则直接向底层大内存分配器申请;如果是小内存,则向小内存分配器申请。这里的划分可以更细致一点,比如以 256K 为界限划分中等内存和大内存。真正的大内存直接从系统分配。因为这种大内存在游戏或者应用程序中分配的次数不多,在其被释放时,我们也会希望它们能被立刻归还系统。我所在的 MMO 游戏项目就是这么做的。

SG 的底层分配器直接使用了 nedmalloc ,自己并不需要做太多的工作。下面主要介绍下小内存分配策略。

小内存分配器管理着一系列的内存池,每个内存池负责分配固定某一大小的内存块。比如 SG 中有 8, 16, 24, … , 256 等 32 个内存池,分别负责分配对应大小字节的内存块。当一个内存申请被发到小内存分配器时,分配器根据内存的大小从对应的内存池中分配内存,如下图所示。

Sagitta Engine 小对象内存分配器

一个内存池的具体分配算法,我参考了 Loki 库的小对象内存分配算法。以 8 字节内存池为例。

Sagitta Engine 小对象内存分配器

上图表示一个初始的内存池状态,全部的 8 字节内存块 ( block ) 均为空闲状态 ( available )。block 之间有类似链表的联系。在 SG 中,实际上每一个 available block 的第一个字节存储了下一个 available block 的索引。最后,我们有一个索引变量 FirstAvailable 总是指向第一个 available block 。那么这个内存池是如何分配内存的呢,请看下图。

Sagitta Engine 小对象内存分配器

上图表示内存池完成一次内存分配后的状态。原先的第一个 block 被占用了,它与第二个 block 失去了联系,而原先第二个 block 变成了此时的 FirstAvailable 。继续分配一个 block ,我们可以得到下图的状态。

Sagitta Engine 小对象内存分配器

现在,我们看下内存释放的过程。假设第一个 block 将被释放,即被设置为 available 。我们将让它指向当前的 FirstAvailable ,并且改变 FirstAvailable 值为被释放这个 block 的索引 。释放后的状态如下。

Sagitta Engine 小对象内存分配器

这样虽然内存池内 block 之间的连接并不一定能回复到初始的状态,但通过 FirstAvailable ,我们依然能访问到所有的空闲 block 。

需要注意的是,为了更节省内存,我们只拿 block 的第一个字节记录下一个 available block 的索引,所以一条 block 链最多只能放下 256 个 block 。因此还需要一个结构来把所有的分配同样大小的 block 链组织起来,在 SG 中就是 sgMemChocBox ,而一条 block 链由类 sgMemChoc 进行管理。

关于这两个类的名字,我一直觉得内存条长得挺像本文第一张图片里的巧克力,所以就这样了。具体的代码可以在 Github 上下载。https://github.com/pyzhangxiang/SagittaEngine



发表评论?

2 条评论。

  1. 用Xcode6编译引擎的时候,在strtk.hpp的15167-15170行出现重定义错误,貌似是std::string::iterator(std::string::const_iterator)的定义和std::vector有冲突,注释掉15167,15168行后工程能够编译通过,但是引擎渲染有问题,三角面不能正常绘制。

    回复回复
  2. 游戏中常用的内存分配方式 | MystEngine - pingback on 2013 年 5 月 27 日 在 16:28

发表评论

Trackbacks and Pingbacks: