游戏中的内存管理,也可以叫做内存池,好像有些也叫对象池,其实方法不少。我就讲哈我自己做的点点经验和想法。
对应不同的类,使用模板类是肯定的。有些类可能不需要自动调用构造和析构,内存分配器就分出来了,就只负责分配对象的内存。方便起见,还是要有new和delete功能的对应方法,那就另外用一个模板类包装内存分配器,是外部使用的类,New函数取得对象内存后再调用构造函数,也要有malloc和free直接调用内存分配器的对应函数。
内存到底预分配多大?其实不好说,但是也是可预测的,实际测试统计之后还是会找到一个比较可靠的值。其实内存不必一次就分配那么多,虽然最大峰值是MAX,大部分时间使用量都是远小于MAX的。内存按组分配要好些,MAX分成多个组,先分配一个组用到,不够了用完的时候,再要一个组,这样子使用率要高些。组在完全没有使用的情况下是可以被回收的,是否要被回收可以变动。这也不会降低好多效率,组的数量不会很多,而且应该更加内存使用的情况而定,一个组里面包含多少个对象也是可调节的,测试后会有一个较好的值。
大概说哈结构嘛,内存分配器(allocator)有malloc和free,组(memorygroup)的单向链表,也可以用双向链表我是为了节约些内存。通过模板参数把类型(T)、组数(groupsize)、对象数(objectsize)传给组。
下面有简易代码说明:
1 template<typename T,int gs,int os>
2 class Allocator
3 {
4 T * malloc();
5 void free(void * p);
6
7 MemoryGroup * grouplist;
8 };
组是内存分配器的内部类,组才真正调用系统malloc分配整块内存,按类数分给对象内存(memoryobject)数组分别保存地址。
1 struct MemoryObject
2 {
3 T * p;
4 MemoryObject * next;
5 };
6 struct MemoryGroup
7 {
8 MemoryObject * freelist;
9 MemoryObject objlist[cs];
10 MemoryGroup * next;
11 };
组维护一个空闲对象内存链表也是单向链表,当内存分配器需要地址的时候,组就把空闲链表中的一个对象内存返回,并把它从链表中删除。当内存分配器要释放对象的时候,对象指针传递给组,组进行效验是否由该组分配,如果是就简单的找到对应对象内存,添加到空闲链表。
外部使用的内存管理类包装内存分配器,实现了malloc和free直接调用内存分配器的,还有new和delete函数是在取得地址后调用构造和调用析构后再传递指针。为了安全起见,用特例化把new和delete与malloc和free分离开,一个实例化的模板类只能调用其中一对函数。
1 template<class T,int gs,int cs,bool nc,bool ar>
2 class MemoryManager
3 {
4 T * malloc();
5 void free(void * p);
6 };
7
8 template<class T,int gs,int cs,bool nc,bool ar>
9 class MemoryManager<T,gs,cs,true,ar>
10 {
11 T * new();
12 void delete(void * p);
13 };
模板参数的第4个参数(needconstruct)选择使用哪一对函数,第5个参数(autorecycle)决定要不要在组完全未使用时回收组。当然回收组是可以动态改变的一个选择,所以第5个参数可以通过函数参数传入。回收组可以在内存分配器是否对象时检测组的使用情况,为了跟踪使用情况可以在组内部附加一个计数器统计未使用对象数量,初始是设定的对象数,使用时减1回收时加1,这个计数器在以后还可以计数使用率等,作为调试和测试信息输出。
为了使用的安全起见,用宏释放对象指针同时把对象指针赋值为0,因为使用了内存管理器所以与一般的释放宏不同需要传入内存管理。
1 #define FREE(m,p) { if (p) { m.free(p); p=0; } }
2 #define DELETE(m,p) { if (p) { m.delete(p); p=0; } }
基本上就是这么多啦,其实也不是黑复杂的东西。
后面讲哈内存使用的不同情况,组数量和对象数量设定的一点想法。