曾几何时,有一个统一的游戏程序员叫Lancelot。 我会说,这是一个非常热情的人。 他还不知道,但是最终他将面对Unity垃圾回收的最黑暗的一面。
兰斯洛特一直在寻找越来越多的作品。 因此,他努力工作,以获得在游戏行业的巨大机会。
他知道这并不容易。
游戏行业中的这些位置过去是,现在仍然是为少数游戏程序员保留的。 而且他不确定自己是否会达到标准。
但是他坚持并继续提高编程技巧。
兰斯洛特开始从事小型游戏。 他想,也许在某个时候,他将获得他寻找的巨大机会。
几年过去了,直到他获得了等待的机会。 他被要求将大型VR游戏移植到移动平台上。 他像兰斯洛特(Lancelot)一样兴奋,不禁怀疑自己是否
足够胜任这项任务。 这令人生畏,但他接受了挑战。 他知道自己只能从中成长。
兰斯洛特最大的担忧是需要大幅提高游戏性能。 实际上,这是双重挑战。 对于一个
功能不那么强大的平台,他必须将
性能提高20% 。
经过几个月的不间断工作,他终于设法对游戏进行了足够的优化,以达到坚实的性能基准。
然而,一个意想不到的问题迫在眉睫...
统一的分析器向他显示,每隔几秒钟他的帧率就会大幅下降。 这让他感到担心,因为那将不允许他发布游戏。 游戏发布处于危险之中。 那使他完全不舒服。
根据他以前的经验,兰斯洛特迅速怀疑了
垃圾收集器 。 毕竟,他知道在游戏中过多分配临时内存可能会导致这些性能峰值。 就像每个人厨房中的垃圾桶一样,当垃圾量达到其容量的80%时,就该清理它了。
因此,他花了几天的时间来解决烦人的内存分配问题。 他执行了他能想到的所有类型的优化。 对象池,数据缓存,数据结构优化...
这些天的花费优化使他在性能旅程中遥遥领先。 兰斯洛特为自己的工作感到骄傲,但是直到他看到垃圾收集器每15秒仍在运行时,他的担忧才会增加。
在这些性能下降的情况下在VR中玩游戏会使人显得苍白。
“那怎么可能?”他想知道。
经过更多的耐心和挖掘,Lancelot发现了他之前从未见过的第二个内存分配来源。 那些事发生在
第三党图书馆 。
他看了一眼,很快意识到自己处于有史以来最糟糕的位置:该库是封闭源代码。 不仅如此,他还尝试使用Unity的
增量垃圾收集器,但他负担不起性能的代价。
Lancelot的选项已用完。
他感到绝望,但设法保持镇定。 毕竟他一直处在更糟糕的情况下。
他可以对库进行
反向工程 ,然后自己进行内存分配优化。 问题是许可证不允许这种事情。 而且他还太年轻,无法入狱。
他考虑的第二个选项是在堆上
预分配大量内存。 他知道,当堆使用率达到一定百分比时,团结会触发垃圾回收过程。 因此,增加堆应该给他更多的时间来进行垃圾回收。
可悲的是,这还不够。
慢慢地,他感觉好像无法控制局势。 这很艰难,但他又
坚持了下来 。
因此,兰斯洛特想出了一个
疯狂的主意 。 如果他完全禁用垃圾收集怎么办? 那有可能吗? 他觉得自己的想法多么危险。 他不想增加游戏崩溃的可能性。 上一次他检查时,对于球员来说这并不有趣。 也许时代确实发生了变化,但总比后悔好。
最重要的是,他担心推迟游戏的发布。 他不想让他的球员在圣诞节错过这个冠军。 他记得在这些假期里玩EverQuest有多少乐趣。 他不会从球员身上夺走这些。
达到这一点,他除了禁用垃圾收集器外别无选择。
他进入研究模式,发现他确实
可以手动禁用垃圾收集 。 他进行了数十次实验,以查看游戏在不耗尽内存的情况下能保持多长时间。 他做了各种各样的测试来强调系统。 单击各处,四处走动,在不同的应用程序之间切换。
数字开始出现在他的电子表格中:25分钟,28分钟,30分钟……他还指出了堆使用量随时间增加的方式,以确保他永远不会超过安全预算。
有了这些数字,兰斯洛特建立了足够的
安全余量,并准备了一个
原型 。 他将在加载屏幕期间和每隔几分钟手动运行垃圾收集。
他又有了希望。
他彬彬有礼地要求质量检查人员进行数十遍游戏。
内存总是在预算之内。 没有崩溃。 无副作用。
漫长的旅程将他带到了能够
运送游戏的地步。
你猜怎么着? 在圣诞节期间,数百名玩家正在享受它。
一开始,他对这种解决方案不满意。 这是一个冒险的举动,他知道。 但是他设法做到了。
兰斯洛特学会了与
不适相处 。 他学会了更加
务实 。 因为有时候必须要有程序员。
故事有什么响吗? 如果是这样,您的直觉可能是正确的。
那个程序员就是我。
在您需要的时候,这是管理垃圾收集器的方式:
该代码段向您展示了如何禁用自动垃圾收集。 它每分钟以及可能在屏幕转换(淡入黑色)期间手动运行GC流程。
请注意其可能的副作用:
- 崩溃 :如果您的游戏不够安全,将耗尽内存。 更糟糕的是,当您在应用程序之间切换时,操作系统可能会杀死您的游戏
- 更长的垃圾收集时间 :增加堆将使以后的垃圾收集速度变慢
如果您需要产生大量的垃圾,这是一个可以使用的简单方法:
public class GenerousGarbageCreator : MonoBehaviour { [SerializeField] private int garbageCreationRate = 1024; private static int[] _garbage; void Update() { _garbage = new int[garbageCreationRate]; } }
这是您将在探查器中获得的内容:
Unity垃圾收集:基于时间的手动触发器在那里,您看到内存使用率不断增加。 不断增长的堆使用情况突出显示为“单”。 对我们来说幸运的是,我们每3秒运行一次手动垃圾收集器。
您可以在探查器图中清楚地看到此垃圾生成清除周期。 对于那些研究物理的游戏开发人员,您可能会认为它是锯齿波形。
如果您想要该项目的源代码,则知道在哪里可以找到它(扰流器:此处)。
有关更常规的内存优化,您可能对Unity可寻址对象感兴趣。 使用可寻址对象,您可以减少总的内存使用量,从而可以减少触发垃圾回收的频率。 反过来,这将减少您的播放器将遇到的性能峰值。
我期待在2020年与大家一起工作。
鲁本