接续上一篇的讨论,这次接着聊聊UE4++、GC跟C++标准库的关系。
部落格版本: http://dorgon.horizon-studio.net/archives/791
欢迎讨论与指教 : )
==========================
从上一节 (http://dorgon.horizon-studio.net/archives/773) 的内容我们知道官方坚持不将Script导入引擎的原因,但对于稍微进阶一点的程序员可能就会开始思考:既然都使用C++了,为什么还要在引擎中实作Garbage Collection(GC)?直接使用RAII的机制不是能够更进一步的避掉因为GC而产生的效能耗损?
先撇开效能问题,其实导入GC的优点很明显:它帮助我们从复杂的内存管理议题中逃脱出来,进而能够专心在游戏逻辑的撰写上。C++虽然是一个非常强大的语言,但反过来说,却也是非常的复杂。
例如上面所提到的RAII,一般最常使用smart pointer的概念来实现。若没有经过特别的训练的话,则很有可能就会迷失在shared pointer跟weak pointer的使用情境之中。更糟的是,若使用者打从一开始就不知道weak pointer的概念,则有很大的可能会写出物件间循环参照的逻辑,进而造成物件的内存永远不会被释放。
因此,并不是所有的使用者都能够正确掌握语言的特性,为了理解并正确使用这些功能,是需要好几年的经验与养成才有办法好好的挥舞这把强力武器。
而UE4为了将C++复杂的特性从一般使用者中隔离出来,所采用的并不是跟其他大部份的游戏引擎一样导入Script创造一个Sandbox环境的方法。相反的,它在原本的C++语法之上更进一步的设计了一套Reflection(UProperty)跟Garbage Collection(GC)的机制。
虽然导入GC会影响效能表现,但相对的C++整体在使用上的复杂度也降低到跟其他高阶语言差不多的程度。而且,由于UE所实作的这套GC机制是为了游戏这类即时互动性高的系统所设计的,因此里面的GC机制不仅有许多回收的优化机制(见Figure 18),而且其采用的“渐进式”方法不会在GC发动时让整个游戏卡住,意即,每次进行回收的动作不会超过所设定的秒数(默认为0.002秒)。
https://i1.wp.com/dorgon.horizon-studio.net/wp-content/uploads/2017/03/image.png
Figure 18 GC默认设定,于Project Setting->Engine->Garbage Collection选项。其中在Optimization中的Create Garbage Collector UObject Clusters这个选项,在目前的版本(4.15)只支援Material跟Particle。
大部份的应用在这套系统下面其实不会有什么大问题。
当然,C++里面一个非常重要的设计哲学:不要为不需要的功能付出效能上的代价 (Stroustrup, March 1994)。对于更进阶的效能需求,其实我们还是可以使用原本标准库中所有的C++方法。当我们的类别不是继承自UObject体系时,是可以使用自己的内存管理机制。基本上,可以将UE4++想成是标准C++的超集。在4.15版本释出时,引擎更是在全平台支援C++14所有语法的特性。
只是当我们开始深入研究相关的方法的时候,会发现:为什么引擎中实作了许多跟标准C++相同概念的方法?例如TArray对应到std::vector;TMap对应到了std::map。为什么不使用标准库中的方法而要自己实作一套呢?难不成UE4自己实作的方法有比标准库中的还要快吗?
其实,速度并不是主要的考量。
根据官方于论坛上的说法,历史因素才是造成目前设计的主因。要知道,UE4是一个已经存在好几十年的引擎,在第一代引擎释出的那个年代其实也正是 C++发布第一套标准 (C++98)。由于标准刚发布时的各方实作并不是很稳定,各个不同平台下的行为模式还不是很一致。为了保证稳定性,当时的引擎有了自己的一套实现,并且一直延用并演化到现在。
由于现在C++标准库已经非常的稳定,官方是有在思考将引擎中对应的方法全部替换成标准库相关实作的可能性。只是,这部份的变更目前并不考虑在第四世代的引擎中实现。因为,那是在引擎“第五世代(UE5)”开发时才该考虑的事。