Gamebryo 游戏引擎分析(二)内存管理

 

Gamebryo中的内存管理是比较传统的。主要就是这些:定义全局的new、delete、malloc、 free;引用计数;智能指针等等。

全局的几个动态内存管理函数,是通过这样几个类实现的:NiMemManager,NiAllocator,NiMemTracker(继承自NiAllocator);这几个类都是对用户透明的,用户只能通过NiMemoryDefines.h 中的函数实现动态内存管理;NiMemoryDefines中函数的实现,用到了NiMemManager中的接口,NiMemManager中的接口的实现是靠具体的NiAllocator的实例完成的,有时候,你只需要看看有没有内存泄露,那就只用NiAllocator,有时候你要跟踪内存的分配情况,那就用NiMemTracker的实例。。。。。。通过头文件NiMenoryDefines.h中宏定义的方式,Release的版本,将使用编译器的new、delete等“原版”实现——这些都是“标准做法”。我上一篇也提到过,对于一个跨平台(从pc到console应该是跨体系了)的引擎,内存分配如果能够实现不同的策略,就应该比较好,而不是全部依赖于编译器的new和delete。在卡马克的quake 4和后面的ETQW引擎中,就是实现了一个自定义的堆管理器。按照文件里面的说法,原话是这样的:

Gamebryo 游戏引擎分析(二)内存管理_第1张图片

不知道他们是怎么测得这个数据的,搞不好是有很大内存的机器,反正数据看起来有些不可思议,什么时候可以试试。(ps,如果有谁知道还有哪些类似的内存管理的组件,麻烦留个言)。另外,这个分配器还自己管理虚拟内存的页面。。。。

回到Gamebryo,GB就没有这样做,其实可以考虑实现一个,好像很多的引擎都有实现(其实我也不知道),一个开源的rts游戏(spring),也有一个内存管理器,不过我都没有怎么看。

在对象管理方面,是简单的使用引用计数来控制的,举个texture的例子,看一个继承树的分支:

Gamebryo 游戏引擎分析(二)内存管理_第2张图片

继承关系就是这样 NiTexture -> NiObjectNET -> NiObject -> NiRefObject -> NiMemObject(这里“->”读作“继承自”),MemObject没有什么特别的,就是表示的“内存里的东东”,这个东东就是引擎里面需要动态分配内存的对象而已,它重定义了new、delete的操作符。NiRefObject就是加上了计数器的控制,但是需要手工去写具体的操作,比如,你用到这个东西的时候,你就要obj->IncRefCount(),你不用的时候就要obj->DecRefCount();NiObject就是具体的对象了,他有些一些其他的接口,比如分组、拷贝等等;NiObjectNET中的NET的含义:N)ames, (E)xtra Data, and (T)ime,具体的作用,应该很好理解;最后,才是NiTexture,纹理对象。

我觉得这个地方可以展开讨论一下,c++中的内存管理一直是个大问题。好在我们现在有很多的解决办法。在游戏引擎里面,对于Texture, Material, Mesh这类资源,需要非常小心的管理他们,不能出现:1,内存泄露(该删的没删);2,野指针(删了还用,或者引用不存在的东西)。所有的这些错误,都是一个设计问题:“哪个”对象“拥有”某个对象。如果这个问题,在设计的时候就能够合理的解决,那出现内存错误的情况就会大大的减少。就我所知,在游戏引擎里面,大概就有三种方式来管理资源(其实大概的思路都是差不多的):

1. 简单的使用引用计数,智能指针等等,Gamebryo这两点都有了。

2. 引用计数+自定义GC,现在发现很多人都在讨论这个(很多国内的引擎好像都是这样),至于GC是怎么工作的,实现上也有一些区别,有的偏向于后台多线程的,有的偏向于定时的,有的偏向于根据内存使用情况的;反正不管怎么样,需要一个单独的运行时来处理。

3. 全局管理。可以使用引用计数,可以使用智能指针,甚至是可以使用自定义的句柄(比如int index),和第一点的差别就在于这个对象有一个全局的所有者。

这三点可以说各有利弊,性能上面也不好说哪种比较好。关键在于你“习惯”哪种方式。第一、二种方法,好处就是,不需要额外的心智去处理对象的销毁(交给智能指针或者gc去做);使用gc和第一点的区别就是,如果一个资源暂时不用,他不一定会被销毁,因为也许他马上又要被使用,如果现在空间还够,那么我就暂时把它留着。这种情况很常见,在你往返于游戏中的两个不同的区域时,就会发生这种情况。第三种方法,接近于GC的方式(其实在某种程度上就是GC),只是更为精确一些:我可以一次清理掉所有的资源,我也可以在资源还足够的时候保留一些暂时没有引用的对象。这样给程序员更多的灵活,以应付不同类型的关卡,或者不同的平台。

我个人比较“习惯”用简单的GC方式,因为方便。性能上面,因为不是像java那样纯后台的gc,所以应该不会造成资源快速用尽的情况。

反正三种方法都是差不多的,只是一点点的差别罢了。(使用起来都是差不多,实现起来其实差别有些大的)

你可能感兴趣的:(Gamebryo 游戏引擎分析(二)内存管理)