Alt-F4 #15 - 1.1 版本调研  2020-11-27

作者 Conor_, Therenas, 编辑 stringweasel, Nanogamer7, nicgarner, Firerazer,
翻译 wfxr, 校对 Ph.X

目录

本周 1.1 实验版刚刚出炉,我们来看一看新版本带来的 2 个新变化。首先,Conor_ 研究了如何利用新的车站限流功能对他基于 TSM 的工厂做些什么。然后 Therenas 分别从理论和实践层面探讨了多线程更新对传送带的意义。细心点读也许能学到点东西!

车站限流 Conor_

当谈到 1.1 带来的新变化时,我最想讨论的当然是火车站限流功能,因为我钟爱火车!接下来,我将研究新功能解决了什么问题,并介绍此前我是如何处理这些问题的。

天真的小白 Conor_ 曾犯过的低级错误

不久前,我正在建立自己的第一个大型基地,并尝试在 5 倍研究费用(我 喜欢 这样)下攻克 SpaceX 模组。很快,我注意到自己的意大利面条式火车网络存在一些问题。在幼稚和无知的驱使下,我决定给运送相同货物的所有车站共用一个站名,再部署大量的火车运行在其间以保证这些车站都能得到充分的利用。然而事实证明这并不是一个好点子,这个易于实现的系统随后带给我的是痛苦和折磨——自从我搞明白原油处理以后,还没再经历过这样的痛苦。在仅有数个车站且相距不远的小基地中,这套系统可以运行的很好。但基地规模变大时,我发现火车根本不会前往远处的站点。一部分车站水泄不通甚至导致了死锁,而剩下的车站则空空荡荡被闲置。作为一个优秀的工程师,我决定必须拿出一个更好的方案来解决这个问题。于是我 努力地研究,开发,实现更好的方案Reddit 上提问。

有没有人知道什么 Mod 可以把火车分配到拥有相同站名的不同站点,无论距离远近?例如,所有的卸货站都叫一个名字,但每一个不同的站站都有相同数量的列车。

Does anyone knows of a mod that will equally distribute trains between stops of the same name, irrelevant of the distance difference. E.g. All iron unload stops called one thing but an equal number of trains go to each of the different stops.

— Conor_ (2019 年 8 月)

我发现自己原来做了一个白日梦。放弃了先前的想法后,我立马转向了 TSMLTN,告诉自己继续前进。然后 1.1 版本发布了,开发组隆重宣布:“我们给火车新增了一个功能!” 。好吧,开发组,你又把我的注意力拉了过来……

什么是车站限流?

如同 FFF-361 中描述的那样,车站限流功允许你给任意一个车站加上火车数量的限制。FFF 中非常详细地讨论了这套系统(绝对值得一读),但从本质上来说,其实就是让火车只前往那些有空闲位置的车站——这正是年轻的 Conor_ 所期望的。不过这和我的新欢 TSM 相比如何呢?

火车限流设置界面
来源:FFF-361

TSM 是啥,我为什么关注它?

火车供应管理(Train Supply Manager,TSM )是一个 Mod,它可以让车站在满足一定信号条件时调用一辆火车,例如车站停靠的火车数少于某个值时。虽然它还支持更复杂的电路控制,比如仅在原料足够装满火车的时候才调用火车,但我从来没有使用过这些功能。

TSM 的梦想是实现一个完全基于拉取(Pull),而不是推送(Push)的物流系统。在很多小规模的工厂中,推送的确是常用的策略。火车在装货站装填,发往目的地,然后在那里排队卸货。但基于拉取的系统中,火车总是处在可以卸货或者可以装载的就绪状态,只有在需要资源的时候才会被调用(即拉取)。

这里的差异其实是火车被调度的时机。对于推送系统,调度时机取决于车站多久可以装满发车,而与工厂需求资源的多少无关。而在拉取系统中,调度时机是由目的车站决定的,从而可以在实际需要的时候才调用火车。这能让你大幅减少网络中运行的火车数量,并且控制火车在何处排队,以确保不会造成堵塞。尽管不用任何模组也能做到这一点,但那需要建立大型的候车区,仔细地规划等候的火车应该停在哪里,不够理想也不够方便。而在这个系统中,装货车站和卸货车站都可以调用火车,这对大型基地而言尤为重要,也是我如此喜爱 TSM 的原因。

视频中每个装卸车站都只允许停一列火车,以确保不会发生积压。当车站变为空闲时,TSM 会从候车场派一列火车到最需要它的车站。

车站限流功能可以替代 TSM 吗?

有了新的车站限流功能以后, TSM 是否仍然是必需的?为了了解这一点,我在 1.1 原版游戏中重新实现了火车供应管理系统,没有借助 TSM ,但仍然非常成功

视频演示了用车站限流功能实现的火车装卸铜板的相同场景。同样,每个装卸站只有一辆火车,其它都在候车场。

这套新系统之所以运作的非常好而且可以很好地替代 TSM ,是因为在装卸站点腾出足够的空间之前,火车会一直在候车场等候。如果火车是在装货站等候,就可能导致交通堵塞进而限制生产者的输出。而使用候车场使火车拥有了某种意义上的“集结待命区域”,火车在不需要的时候会一直在这里待命,让出铁路网。

正如我们所见,这个系统现在完全可以用原版功能重新实现出来。而且由于这是内置功能,性能可能会更好。更棒的是车站限流功能并没有增加多少复杂性,与之相比,TSM 的学习曲线则要陡峭很多,文档也不是非常完善,上手难度比较高。不过 TSM 仍然有它的应用场景,比如玩家可能想看到当前有哪些物流需求还没有完成,TSM 提供了获取这些信息的界面,不过我个人很少用到。

结论

在支持模组的游戏中,我们经常看到开发人员会将一些模组里面有趣的功能添加到游戏本体中。这对模组作者来说可能有些残忍,毕竟这很可能会让他们的模组因此而过时。但无论如何,最终的结果是他们成功地改进了游戏。制作者们的模组现在已经间接地内置到了他们热爱的游戏当中,意味着会有更多的人可以接触到并使用它,这是很棒的一件事。

非常感谢异星工厂 Discord 服务器的 sorahn 发现了我的一些问题,并竭尽所能地为我提供帮助。他制作了上文中的地图,我在它基础上做了一些修改来演示 TSM 如何以“双重请求”的模式工作,并在我建造它们前进行了健全性测试。

这个新功能对新手(比如天真的小白 Conor_)来说是非常惊艳的。它让我们可以更轻松地建立更大,更精密的铁路网。而对于那些专业玩家来说,这也是一个易于上手但难于精通的功能,可以供他们使用和探索,同时在帮助新手中获得乐趣。

1.1 性能改进 Therenas

异星工厂的最新实验版带来了许多变化,我今天准备仔细研究其中之一。它被轻描淡写地隐藏在 1.1.0 更新日志中,而且从未在任何发布前的 FFF 中提及过。关于这项改进的描述只有寥寥几个字:传送带多线程更新逻辑。在这里我想研究一下这段话究竟表达了什么意思,以及它有什么实质性的影响。

技术层面上,这项优化是如何实现的?

你可能现在对多线程游戏逻辑的含义没有什么概念。为什么不在所有的地方都使用多线程呢?这样游戏就可以充分地利用你 PC 的所有核心。不过,事实并非如此简单。通常游戏在每一个时刻都需要更新所有的组装机,传送带,管道等等。这就是游戏中时间进展的实际方式,能让游戏运行起来的第一要务。这些事件发生的顺序非常重要。首先传送带沿着其方向移动物品,然后机械臂拿起物品并放入组装机,接着组装机用它来制作某样物品。

多线程带来的根本问题是它无法保证事件的发生顺序。当你将上面的实例改为多线程时,有可能机械臂还没有把原料放入其中组装机就尝试进行制造了。在这种情形下,组装机因为缺少原料而无法工作。相反如果机械臂放入原料先发生,组装机就可以正常工作。这是有问题的,因为它存在不确定性。从多线程导致的结果来看,组装机有时可以进行制作而有时却不行,这会破坏整个模拟流程。

当然,这个实例仅仅是为了说明问题所在。真实情况本质上远比这复杂,也更具技术性。另外,我举例中用到的那些动作在真实情况下也并不会在同一个时刻,同一台机器上发生。这只是一个来说明问题的比喻,并不一定对应游戏实际的处理方式。

所以从表面看,在异星工厂这样的游戏中好像无法使用多线程,因为这会破坏模拟流程。游戏中的所有事物都依赖于其他事物,不是吗?其实并不完全是。固然有很多地方需要严格按照线性方式来执行,但如果进一步观察,你会发现存在一些整体上完全独立于彼此的部分,传送带的逻辑就是其中之一。

考虑一下你就会发现,地图上的每一条传送带彼此之间是没有关联的。当然,会存在一些巨型的传送带网络内部是互相连接的, 比如你运行了一个总线。但尽管如此,还是有不少独立的传送带,它们并没有和其他传送带连接在一起。实际上火车和成排的组装机都很可能切断传送带之间的连接。

2 条独立的传送带交叉在一起的截图
截图中的蓝带和红带是不同传送带的一部分。注意它们彼此交叉,但没有发生实际交互。另外注意组装机是怎样使产生间接交互又彼此独立,以便于多线程处理的。

这一事实可以允许我们把传送带更新的逻辑并行化(即多线程)。不过我们必须要小心,因为这并不意味着我们可以在每个时间周期内按任意的顺序更新传送带。移动物品,抓入物品,组装机制造,这些步骤顺序仍然是必须的。我们可以做的是拆分物品的移动这一步骤。意识到了这一点,我们可以将任务分解,让每一条独立的传送带拥有各自的线程。之后,每个线程负责移动分配给自己的传送带上的物品,从而实现了让物品同时进行移动,也就是所谓的并行处理。只要我们小心地将不与其他部分交互的传送带分割成组,就可以安全地在各自的线程上对其进行更新。

游戏更新流程的相关环节示意图
上图展示了如何提升性能。游戏的等待时间取决于耗时最长的单元(本例中的传送带 #2 ),而不是所有部分更新时间的加和。实际中不止有 3 条独立的传送带,所以这可以带来很大的提升。

这一方法和 FFF #271 中讨论过的改进流体更新逻辑的方法非常相似。那篇文章在诸如如何更改内存布局以提高缓存效率等方面也提供了很深的见解,但这不是本文的重点。Varen/Raven 在 Reddit 上的一个帖子也很有意思,他们在从一开始就考虑多线程的情况下讨论了异星工厂的重新实现,你可以读一读来进一步了解有关此主题的技术讨论。

核心问题: 实际提升如何?

所有这些理论都很不错,但你一定在好奇这对实际性能有多大提升。好了,现在不用好奇了,我已经做好了图表!

免责声明,这些评测分别在 1.0.0 版本和 1.1.1 版本上进行。我用游戏内置的控制台调高了游戏速度,可以让游戏获得超过 60 的 UPS。这些数据不是用非常严格的方法测得的,所以误差是不能忽略的。还有一点需要考虑的是,更新日志里面提到了实体更新性能也得到了提升。综合这些因素,我无法保证自己的评测精度可以达到得出明确结论的程度。

我对 3 个不同特征的存档进行了基准测试,它们的共同点是都使用了大量的传送带——毕竟你不可能在不存在的地方得到性能提升。接下来让我们来认识一下参赛者,它们都来自 FactorioBox 这个网站。这个网站非常好用,里面有一些适合用来测试的地图。

我首先测试了 Stevetrov 的 10 千瓶基地。这个存档使用了对性能优化非常友好的布局,并且几乎完全依赖传送带,没有火车,机器人也只有在被证明其性能优于传送带的一些特殊情景中才使用。这些因素让这个存档成为我们的一个理想选择,来展示改进所带来的变化。其他场景中,性能成本会过多的分散到火车或机器人等事物上,会使得效果不是那么明显。

描述了 1.0 和 1.1 中的各种性能指标的图表 描述了 1.0 和 1.1 中的各种性能指标的图表
如图所示,在我们首个相对理想化的示例中,传送带更新时间的改进是非常可观的,达到了 150% !整个地图的 UPS 提高了约 26% ——这仅仅是传送带相关的改进带来的提升。有趣的是,我们看到实体的更新时间略有增加,但处于测量的误差范围内,并不能由此得出什么结论。

然后我测试了一个布局更接近你我的基地,它在 FactorioBox 上的名称是 cam6,来源无从考证。它主要依靠的仍然是传送带,但是混有一些火车和机器人。这个基地还使用了对性能不那么友好的核电。就像我说的那样,这里面有你在一个通常的异星工厂地图中见到的一切,所以它可以很好地反映性能改进对你自己工厂的影响。

描述了 1.0 和 1.1 中的各种性能指标的图表 描述了 1.0 和 1.1 中的各种性能指标的图表
来看一看我们更熟悉的地图表现,传送带的更新时间改善了约 100% ,虽然对比 10 千瓶的大型基地稍差,但仍然是一个很显著的提升。UPS 提升则在 16% 左右。实体更新时间的变化同样在误差允许的范围内

最后,我研究了一个有点不同寻常的候选者:一个巨大且凌乱不堪的地图。它的标题是 Besenovsky Pajzel ,大概得名于是它的作者。它被描述为一个“2.4 到 4 千瓶,生产力低下但尺寸庞大的地图(13300x7400 格)”。这张地图混合了所有的东西,与前两者最大的不同是它广泛使用了火车。我们由此可以预见的是,1.1 版本的优化对其影响较小,因为改进的地方与这张图的关系相对不是很大。

描述了 1.0 和 1.1 中的各种性能指标的图表 描述了 1.0 和 1.1 中的各种性能指标的图表
最后一组图表显示,这个更混乱的基地竟然得到了 170% 左右的巨幅提升,甚至比之前那个大型基地提升更多!

如果我们把这三个非常粗糙的基准测试综合在一起来看,可以得到传送带性能平均提升了 140% ,UPS 平均提升了 26% 。当然这并不能代表所有存档的平均情况,因为我们仅考虑了这三个特定的地图。总而言之,1.1 版本的性能提升幅度在很大程度上取决于你的基地布局,但可以肯定的是这是一个不错的整体改进。

其实一项特定的性能提升是否会产生重大的影响并不重要,所有细小的改进加在一起才可以使游戏运行快一个数量级。几周前,我们在 Alt-F4 #13 中探讨了这一效应,我估计那个基地可以获得额外的性能提升。

征稿

一如既往的,我们正在召集任何想要为 Alt-F4 做出贡献的人,无论是提交文章还是帮助翻译都可以。如果您有些有趣的想法,并乐于与社区分享,这里就是一个好地方。如果您没有太大把握,我们会很乐意帮助您讨论内容创意和结构问题。如果您有意参与,从加入 Discord 开始吧!