异星工厂吧 关注:60,752贴子:388,584
  • 19回复贴,共1

周五工厂报#421——优化 2.0

只看楼主收藏回复

投稿者 Rseding,boskid,kovarex 于 2024-07-26

你们好,
我们都喜欢造越来越大的建筑,但最终会一头撞上 UPS 天花板,真是伤心。
因此,我们必须继续永无止境地优化游戏。
--------------------
机器人指令平台优化 ~ Rseding
在异星工厂上班的这些年里,我分析了很多存档,并且经常看到一些存档中物流机器人和 / 或建造机器人会吞掉大量 UPS。这不是什么新鲜事,但随着作业机器人的问题出现,机器人指令平台的问题也出现了——因为数量众多。

一个典型的工厂,有一大堆机器人指令平台
机器人指令平台从来都称不上“慢”,但它们始终存在,而且游戏鼓励建造一大堆机器人指令平台——在即将到来的《太空时代》中还要更多,因为你需要远程做很多事情。在最近的游玩测试中,结束时的存档再次显示了它们会花一些很小但非零的时间,因此我又一次开始思考它们。
如果它们不需要每时每刻都保持活跃以更新,那就太好了。大多数机器人指令平台除了耗电以外啥事不干,偶尔(相对于它们的一生)会有一些机器人过来充电 / 停泊。但是大多数指令平台除了作为物流网络的延伸而存在以外,并不会做其它事。所以我做了一个实验:在它们不需要做任何特别的事的时候关闭它们,怎么样?如果有个机器人过来充电了,或者在罕见的情况下需要做其他事,再把它们打开,直到事件结束。
当然除此以外还有其它复杂的地方,但最终实验结果是有效的。
在我们最近的游玩测试的存档中,在机器人指令平台上花费的时间从平均每游戏刻 1 毫秒降低到了每刻 0.025 毫秒。
--------------------
雷达逻辑优化 ~ Rseding
这个月早些时候,待办列表中加入了一个小功能:向机器人指令平台中加入一个“小型雷达覆盖”区域。通常这种功能 5 分钟就能搞定,但这个功能还有一个规定:“重叠区域不要消耗性能。”
在此之前,雷达这个设施,以及内置于其它东西(如玩家)里的雷达功能非常简单,只是“每隔一段时间,在它周围挨个选择一个块,然后要求地图系统保持显示这个块。”大多数情况下效果很好,因为一般不会有太多重叠的玩家和雷达。但现在,我们想要机器人指令平台——在紧密的集群中建造,并且有很多重叠的设施——来做同样的逻辑。
我选择了一种注册风格的系统,在这个系统中,任何想要显示地图的某个块的东西,只需要递增地图系统中的那个块的计数器,这样那个块就会被标记为“保持显示”。因此那些东西可以随心所欲地重叠,这只会增加计数器。

显示“保持显示”计数器的调试选项。
如果一个块的计数器大于 1,那么它会被放入更新桶之一,这个选中的更新桶会在预先创建的更新桶之间循环,每个游戏刻循环一个。
当某个块的计数器返回到 0 时,它会被从桶中移除,并停止更新。

遍历桶以保持块被显示。
(0.5 倍速)
有了这个新系统,设施雷达的逻辑,以及其它实体的雷达逻辑,都可以替换了,因此它们共享同样的地图注册表。这还产生了意外的效果:雷达设施现在占用更少的 UPS,而且因为机器人指令平台会提供雷达覆盖范围,就是说没必要用那么多雷达了,雷达设施总共用时还会更少。
在另一个游玩测试的存档中,我们发现这个新系统使整体游戏性能提高了 3.6%。因为这个新系统并不会有什么可测量的影响(如果有的话,我估计添加指令平台的雷达覆盖会让情况变得更糟),3.6% 的整体改进非常棒。
所以这个功能很成功,在 2.0 中,机器人指令平台将会有范围 2 个块的小型“内置”雷达。
--------------------
照明灯始终开启 ~ boskid
在我们最近的办公室局域网派对中,我们在一家餐厅吃饭的时候,kovarex 分享了一个想法:因为可以选择任何 RGB 颜色的照明灯(工厂报#388),他想用照明灯显示图片。唯一的问题是为了供电必须时不时放一些广域配电站,所以图片稍微有些难看。这时我说“要不把灯放到太空平台上”,因为太空平台上不需要电线杆。他好像挺开心而且有点激动,但我保持冷静,因为不知道明天会发生什么。
第二天的游玩测试到来时,他造了一个 100 宽 x 150 高的蓝图,包含要放置在太空平台上的照明灯,这些灯里面包含被转换为 RGB 颜色的图片。可惜,有一个重要的问题:太空平台上总是白天,所以照明灯不会打开。必须要把灯连到信号网络上才能强制在白天打开灯。我们很快就注意到了这件事,因为在我们快速增长的存档中,UPS 降得太快了。

其中一个灯由信号强制开启。
这里有 15000 盏灯,它们的信号控制的行为需要在每个游戏刻都进行更新,这并不是最佳选择,所以我让照明灯即使在白天也可以强制开启。

使用始终开启的照明灯。
仅仅是跳过了把灯连接到信号网络,我们的存档就更新得快了约 1.2 毫秒。但是,通过查看更改后信号控制的行为占用的更新时间,我有点担心为什么信号控制行为还是占用了这么多时间,所以我不得不解释它。
--------------------
传送带读取器和多线程信号控制行为 ~ boskid
信号控制行为占用时间过高很容易解释。这是我实现的功能:读取所有连续传送带的内容。(工厂报#405)
添加传送带读取器的原因类似于添加照明灯始终开启的原因——为了减少活动信号控制行为的数量,因为每一格传送带都必须单独连接才能读取其内容。现在可以读取所有连续传送带内容,这意味着只需要把其中一格传送带连接到信号网络上,只有一个信号控制行为需要计数物品,传送带线路不需要分成一格长的块。实际上,添加传送带读取器,可以显著减少信号控制行为和传送带线路造成的 UPS 损耗,同时还可以获得以前无法实现的新功能(如计算地下传送带中的物品)。
传送带读取器的问题在于它太容易使用了,我们在游玩测试中大量使用它,不仅在预期的太空平台上,而且还在其它地方用。

传送带读取在我没想到的地方使用。
可以说 Hrusa 是我们游戏测试的主要破坏者。他在本可以不使用传送带读取器的地方偏要使用它。他还在 Fulgora 上放了一些主动供货箱(紫箱),把 Fulgora 变成物流机器人地狱,每时每刻都有一万多个机器人在飞。我不能因为他这样玩游戏而惩罚他,所以是时候优化了。优化物流机器人的代码是别人的工作,我要做的是优化传送带读取器和信号控制行为。
传送带读取器的内部结构非常简单:每游戏刻它会检查传送带上有哪几种物品,以及每一堆(工厂报#393,传送带上物品可堆叠)里有多少物品,然后计算总数。
优化传送带读取器经过了几次迭代。其中一个试图保持传送带上当前的总物品,但失败了。
对我来说,转折点是意识到一个非常简单的事实:传送带读取器主要是一个“读取”操作:它只是读取内存中的数据(传送带堆叠的内容)以计数物品,最后产出一帧的信号并发送到信号网络。这个结构表示应该可以多线程处理:同时计算的多个传送带读取器不会相互干扰,因为它们只会读取传送带内容,而且输出不被用于其它传送带读取器。可以在其它地方看到类似的结构,如读取物流网络内容的机器人指令平台,算术运算器,判断运算器和选择运算器(工厂报#384)。除了选择运算器(因为不能再使用全局随机生成器了),所有这些都相对容易实现多线程。
完成这些更改后,我们的游玩测试的存档的运行速度提高了 9.5%。
一个装满了 7 万 7 千个运算器的人造存档,这些运算器和我在开发中使用的 6 千信号网络互连,这个存档的运行速度变成了原来的 14.9 倍,同时 CPU 使用率不变,始终为 100%(基准测试时间从 131 秒降低到了只有约 8.2 秒)。
--------------------
失败的尝试:多线程电力网络更新 ~ boskid
这是我从大家那里不断听到的事情之一:多线程更新电网。
电网更新已经得到改进(工厂报#209),然而它还是只由一个线程执行,而且这个线程通常是完成速度最慢的一个。在大多数的存档中,整个游戏只有一个巨大的电网,所以多线程没多大用。然而在《太空时代》中,有许多大型电网(不同的星球),因此多线程的潜力更大。
对于任何多线程的东西,首先得辨别出移动部件以及它们如何相互交互。所有这些都是必需的,因为游戏需要保持完全确定性,否则会发生不同步。
乍一看,这应该非常简单,每个线程处理不同的电网,这就完成了。然而,事实并非如此,因为有一种游戏机制让它稍微复杂了一点:一个设施可以由多个电网供电。

电网不独立的情况之一。
在这个例子中,当炼油厂正在炼油时,其储存的电能被释放,需要再次充电。这里有 2 种可能发生的情况:
- 左边的电网先更新——用蒸汽机为炼油厂充电,锅炉会烧燃料,爪子会启动。
- 右边的电网先更新——用太阳能板为炼油厂充电(这种情况下锅炉保持空闲)。
因此,电网相互依赖,并且必须在同一线程中更新。
在找到所有此类情况(例如电闸关闭导致多个网络合并)后,我能够定义更新组应包含的内容。如果两个电网至少有一个实体同时由它们俩供电,则这些电网必须位于同一更新组中,由同一线程更新,因此不会发生不同步。
我终于能够开始进行测量……
结果
这就是这个想法完全失败的地方。我们的游玩测试存档中有至少 4 个完全独立的大型电力网络,因为它们位于不同的星球上,但是在 CPU 使用率显著上升的情况下,电网更新时间保持不变。
事实证明,电网更新并没有做太多事情:它只是读取两个变量,做一两个加法,然后进入下一个实体。它受内存吞吐量限制,并且要是想使用更多线程从内存中读取数据,处理器无法简单地更快地读取数据。在这里使用多线程,而不是让一个线程等待内存,所有更新电网的线程都在等待内存,从而拖慢彼此。
为了完全拒绝这个想法,我不得不使用额外的分析工具,比如英特尔的 VTune,它使我能够给出更多的数字论据,表示电网受内存吞吐量限制。我们的游玩测试存档的电网更新时间从 0.5 毫秒提高到 0.39 毫秒,而 CPU 使用率从 0.5% 提高到 15%。总体而言,存档的运行速度没有更快。
--------------------
作业机器人更智能更新 ~ kovarex
我注意到我们办公室存档里的物流机器人存在一个问题:Fulgora 上有个自动向指令平台中添加机器人的系统,有人从中断开了一根线缆。几个小时后,产生了一个机器人过剩的物流网络,一万个机器人无处可去,因为所有的机器人平台都满了。
由于几个小时内没有人注意到这个问题,因此第一反应是在工人机器人没有任何空间停泊时添加警报,类似于物流网络存储空间不足的警报。

无家可归的机器人在机器人指令平台边挤成一团。
但根本的问题是,作业机器人的更新过于简单。所有机器人在每游戏刻都会进行迭代,并执行其更新逻辑。然而,大多数时候,更新逻辑非常简单,比如“继续朝着这个目标前进”、“在队列中等待充电”等。
这不是一个新想法,假装某物的平稳运动,而实际上只是一段时间才更新一次。(译注:这跟上文有关系吗?)我们在 9 年前使用了块更新计划器 (FFF-161) 和烟雾更新技巧 (FFF-84),因此将这些技术应用于作业机器人只是时间问题。
与烟雾相比,作业机器人的问题在于,它们做了很多不同的事情(不同类型的工作、停泊、充电等),但由于我有动力寻找另一种优化游戏的方法,所以我坐下来,立即开始了优化。
挑战的主要部分是机器人的移动,因为大多数逻辑都以简单的方式工作:
- 到了吗?
- 没有!再近点。
- 到了吗?
- 到了,执行逻辑的下一部分。
但是现在需要将移动意图存储在机器人中,并在我们想要移动到某个地方时使用它。一旦知道移动意图,机器人就可以进入过渡状态,渲染代码可以使用这个意图来假装机器人一直在平稳移动。这也可以用于计算到达目的地的时间,以了解何时安排下一次更新。

调试可视化:红色(真实机器人)20 刻才更新一次。幽灵机器人是用于渲染的预测位置。
需要有一些限制,因为机器人可以移动很长时间,也许一些远处的机器人会进入我们的屏幕中心,但我们不会知道它,因为它的真实位置太远。出于类似种类的不同原因(例如,灯在屏幕外发光),我们也会检查屏幕的边缘,因此有一些自由活动的空间。正因为如此,我刚刚决定定义一些硬编码的魔数,最大 20 游戏刻的不更新的移动,和最大 60 游戏刻的不更新的静止机器人。这些可能会增加,但此时实际性能的提高递减得十分迅速。
结果
在办公室局域网派对存档中,存档的整体性能提高了 15%,一般取决于机器人的数量,在一些重度机器人存档中,整体性能提升通常在 10-25% 左右。我认为这是一次成功。
随着所有挑战的结合,在拥有更大的《太空时代》存档时,我们在性能方面进入了更舒适的领域。但是,如果能再推动一点,让玩家更加疯狂,那就更好了。我们有一些想法可能会有很大帮助,所以请继续关注,并希望这些将是成功的故事,而不是失败的事后分析。


IP属地:浙江本楼含有高级字体1楼2024-07-27 13:24回复
    这期也挺长啊


    IP属地:吉林来自Android客户端2楼2024-07-27 16:33
    收起回复
      这才是官方该干的事。优化底层 增加新机制。而不是把已有的mod干过的事再干一遍。之前出的那个聚变堆。不能说不好,但是起码四五个大mod里都有聚变,官方做的也没比他们更有创意。


      IP属地:北京来自Android客户端3楼2024-07-27 19:30
      收起回复
        这个好,我对SA没啥期待,最想体验对还是这些底层逻辑的优化,还有那个立交桥,终于可以正儿八经玩海运了


        IP属地:江苏4楼2024-07-27 22:00
        收起回复
          讲道理这期ff在QQ群里讨论不是很热烈,但其实这些优化还是很重要的.
          以雷达为例,我为了节省ups找的mod改成显示范围100*100区块大小,但这些都不如官方从底层节省雷达ups占用


          IP属地:黑龙江5楼2024-07-28 13:58
          收起回复
            机器人扎堆充电 优化了吗


            IP属地:泰国6楼2024-07-28 22:25
            回复
              唯一见过的会跟玩家聊游戏底层的游戏开发商


              IP属地:江西7楼2024-07-29 09:34
              收起回复