UE3垃圾回收机制

原文地址

  要进行垃圾回收,有两个条件:一、要知道所有的对象放在哪里,即内存中的位置。二、要知道对象的这块内存上,数据表示的是什么意思,是一个Float数还是Int数还是一个对象指针的值。具备这两个条件,才可以遍历所有的对象,找出没有被引用的对象,然后删除释放掉。

  U3是如何满足这两个条件的呢,先看第一个。 U3里几乎所有的类,都以UObject为基类,一般核心基类都尽量做得简洁明了,但UObject却很复杂,有大量的static成员变量和成员函数,来实现包括对象构造、对象管理、垃圾回收、资源加载等功能。

  首先看GObjObjects这个static TArray<UObject*>指针数组,记录着游戏启动后构造的一个一个对象的地址。对象的构造都是通过StaticConstructObject()函数进行,它首先根据名字查找,该对象是否已经存在,若不存在,则调用malloc()申请内存,然后设置DefaultProperties值啊Config值啊等等,然后调用AddObject()将对象放入GObjObjects数组中,同时也放入GObjHash的哈希表中,方便以后查找对象。当然,不会直接放入GObjObjects数组的最后,而是有一个TArray<int> GObjAvailable数组,记录着GObjObjects数组中为NULL的项的Index,这些是被垃圾回收的对象留下来的坑。

  所以一个对象放入数组中后,其Index值就永远不会变,直到本身逝去,死后留坑。 所以要遍历所有的对象,只需要遍历GObjObjects数组即可。那如何判断对象内存中的数据,是表示Float还是int还是对象指针呢?UE3是用C++写的,C++本身不支持元数据啊,那怎么办呢,关键就在脚本。脚本一般用来编写游戏逻辑,方便之用,UE3却是以脚本为根基。几乎所有的类,都需要先定义在脚本里,然后编译自动生成.h头文件,然后手工添加.cpp文件来实现native的函数。编译脚本的时候,会记录类中所有变量和函数的元数据,保存在生成的.u文件中,运行加载.u的时候,每个类每个变量每个函数都会有相应的UObject对象与之对应,就相当于元数据了。比如Actor类中的DrawScale变量,是一个Float类型,以一个UFloatProperty对象来表示,UFloatProperty继承自UProperty,UProperty继承自UField,UField继承自UObject。为了记录一个变量的信息,继承了这么多层次,而且UObject又是那么庞大,真是大方。所以游戏运行起来的时候,先构造一大堆的UFloatProperty,UIntProperty,UStructProperty对象放入GObjObjects数组中,当脚本数也就是类的数量很多时,这一大堆就很壮观了,繁荣程度稍后再讲。 好了,现在可以遍历所有的对象,又能根据从脚本中来的元数据来判断数据类型,就可以进行垃圾回收了。

  垃圾回收分为二步,一是遍历所有对象,给需要回收的对象打上标识。 具体过程是,遍历GObjObjects数组,找出有RF_RootSet这些标识的对象,放入一个数组中,然后将其它所有的对象标识为RF_Unreachable。然后遍历刚才的数组,分析对象中成员变量的类型,比如是GCRT_Object类型,则知道它是一个对象指针,然后将它指向的对象清除掉Unreachable标识。这样做完后,还有RF_Unreachable标识的对象,就是没有再被引用的对象。

  第二步水到渠成,回收对象,释放内存。

  这就是内存回收的大概印象了,若论起细节来,其实也还没看懂。 刚才未说完的UFloatProperty这些对象,到底有多少呢?写了个小函数,遍历GObjObjects数组,将所有对象的FullName和Index打印出来,一看不得不,对象总共近9万个,而UFloatProperty这类对象,居然约有6万个,足足占三分之二,真是大手笔。

  这些元数据的UObject对象,肯定会一直存在,不需要垃圾回收,所以应该在对象遍历阶段,跳过这些对象。UE3已经定义了一个变量GObjFirstGCIndex,遍历GObjObjects数组的时候,就是从这个下标开始。只是这个值的默认值是0,把它改为6000,然后略测了下,遍历所花费的时间,由0.28s降到0.12s,效果显著。不知道用UE3做游戏的,是不是都需要根据项目中类的实际数量,来手工设置这个变量?有机会去发个邮件问问倒是不错。  

  PS:后来再看代码,原来引擎里已经设置了,如果是seekfreeloading模式,则使用DefaultEngine.ini中MaxObjectsNotConsideredByGC的值。这个值应该配置为多大呢,根据UObject::StaticInit()里的注释,MakeObjectsToDisregardForGC()函数里统计了属于Root Set的对象并记录在日志中了,根据日志中记录的数值,来配置MaxObjectsNotConsideredByGC即可

转载于:https://www.cnblogs.com/flycodes/articles/2721367.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
    游戏引擎中之所以要做内存管理,一个是加快内存分配速度,另一个就是处理内存泄漏问题。     1.先简单说处理内存泄漏这个问题,一般的引擎在debug 模式下 都有一个记录内存分配的结构体,每分配一段内存就记录这段内存的信息,包括大小,分配时间,是否是数组,前后越界的标记等等吧,其实这些都不是那么重要,因为你只知道这些,一旦泄漏出现,你虽然知道泄漏,但无法定位。相反如果你知道堆栈的调用信息,就能准确定位。我以前的实现,在debug下,只记录当前调用new的时候的行号和文件,也就是内部的__FILE__ __LINE___.。我看了同事那个能记录堆栈调用过程,简直觉得很牛逼(其实不是调用堆栈,只是打印出调用过程,继续往下看你就知道了),以前自己也想过,但不知道怎么去实现。如果U3里面也加入这个功能,那就更牛叉了。思想很简单,就是核心东西在一个函数,这个是系统函数,提供当前这行指令所在的地址,它会打印出来这行指令的文件名和行号。先详细说下数据在内存的分配     最早的计算机数据段和代码段区分的很严格,现在似乎没有这么严格了!对于全局变量和静态变量它的分配完全在数据段分配,知道运行结束才会收回内存!而对于自动变量(包括函数参数和函数中定义的变量)则在堆栈中分配!一般的分配情形是这样的:从栈下到栈顶依次是函数参数,函数结束后下一条指令的地址,寄存器保护,然后是函数中定义的变量!
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值