打破统一垃圾收集规则

曾几何时,有一个统一的游戏程序员叫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年与大家一起工作。
鲁本

Source: https://habr.com/ru/post/zh-CN484710/


All Articles