在对优化进行了一点研究之后,我发现(几乎在所有地方)过早优化游戏似乎是普遍公认的罪过。
我真的不明白这一点,在最后改变游戏的某些核心结构,而不是在第一次考虑性能的情况下开发它们,是否会变得异常困难?
我得到等待,直到游戏结束,它会告诉您是否甚至需要优化,但是您还是不应该这样做,毕竟,它可以扩大游戏可以运行的设备的种类,从而增加潜在的数量玩家。
有人可以向我解释为什么过早优化是一个坏主意吗?
在对优化进行了一点研究之后,我发现(几乎在所有地方)过早优化游戏似乎是普遍公认的罪过。
我真的不明白这一点,在最后改变游戏的某些核心结构,而不是在第一次考虑性能的情况下开发它们,是否会变得异常困难?
我得到等待,直到游戏结束,它会告诉您是否甚至需要优化,但是您还是不应该这样做,毕竟,它可以扩大游戏可以运行的设备的种类,从而增加潜在的数量玩家。
有人可以向我解释为什么过早优化是一个坏主意吗?
Answers:
前言:
评论中提出了一些反对意见,我认为这很大程度上是由于人们对我们所说的“过早优化”的含义的误解-因此,我想对此进行一点澄清。
“不要过早优化”并不能意味着“你知道写代码是错误的,因为高德纳说你不准把它清理干净,直到最后”
这意味着“除非您知道程序的哪些部分实际上需要更快的帮助,否则就不要牺牲时间和可读性来进行优化。” 由于典型的程序将大部分时间都花在了几个瓶颈上,因此投资优化“所有内容”可能不会像将相同的投资仅集中在瓶颈的代码上那样获得同样的速度提升。
这意味着,如果有疑问,我们应该:
首选易于编写,易于理解且易于修改的代码
检查是否需要进一步优化(通常通过对运行的程序进行概要分析,尽管下面的注释说明了进行数学分析的唯一风险-您还需要检查数学是否正确)
过早的优化不是:
以适合您需求的方式构造代码的架构决策-以经过考虑的方式选择适当的模块/职责/接口/通信系统。
简单高效,无需花费额外时间或使您的代码难以阅读。诸如使用强类型输入之类的东西可以既高效又可以使您的意图更加清晰。缓存引用而不是重复搜索是另一个示例(只要您的案例不需要复杂的缓存无效逻辑-可能要等到您首先介绍简单方法后再进行编写)。
为工作使用正确的算法。与穷举搜索寻路图相比,A *更优化,更复杂。这也是一个行业标准。重复主题,坚持这样的久经考验的方法实际上比使代码简单而不符合已知的最佳实践,可以使您的代码更容易理解。如果您有遇到在以前的项目中以一种方式实现游戏功能X的瓶颈的经验,则无需在该项目上再次遇到相同的瓶颈即可知道它是真实的-您可以并且应该重复使用过去有效的解决方案游戏。
所有这些优化类型都是合理的,通常不会被标记为“过早”(除非您不愿为8x8棋盘图实施尖端寻路...)
现在,我们将这些问题清除,以了解为什么我们可能会特别在游戏中发现此政策有用:
特别是在gamedev中,迭代速度是最宝贵的。我们经常会实施和重新实现比最终游戏要附带的想法更多的想法,以试图“找到乐趣”。
如果您能以一种简单明了的方式(也许有点天真)对机械师进行原型设计,并在第二天对其进行游戏测试,则比起花一个星期的时间首先制作出最优化的机械师而言,您处于更好的位置。尤其是如果事实证明很烂,而您最终却放弃了该功能。以一种简单的方式进行操作,以便您可以及早进行测试,可以节省大量不必要的代码优化工作。
与未经微调的代码相比,经过优化的代码通常可以更轻松地修改和尝试不同的变体,这些代码经过微调可以最佳地完成一件精确的事情,而这种代码往往很脆弱,并且难以修改而不会破坏代码,引入错误或减慢其运行速度。因此,在大多数开发过程中,保持代码简单易更改通常值得一点运行时效率低下(我们通常在高于目标规格的机器上进行开发,因此我们可以吸收开销并专注于首先获得目标体验),直到我们已经锁定了我们需要的功能,并可以优化我们现在知道很慢的零件。
是的,要在开发的后期重构项目的一部分以优化慢点可能很难。但是在整个开发过程中也会重复进行重构,因为您上个月进行的优化与自那时以来游戏的发展方向不兼容,或者在您获得更多功能和内容后正在修复的东西并不是真正的瓶颈在。
游戏既怪异又是实验性的-很难预测游戏项目及其技术需求将如何演变以及性能最严格的地方。在实践中,我们常常会担心错误的事情–在此处搜索性能问题,您会发现一个共同的主题是,开发人员被纸上的东西分散了注意力,这可能根本不是问题。
举一个具有戏剧性的例子:如果您的游戏是GPU限制的(并非罕见),那么花在超优化和线程化CPU工作上的所有时间可能根本不会带来明显的好处。所有这些开发时间本来可以用于实现和完善游戏功能,以获得更好的玩家体验。
总体而言,您花费在游戏上的大部分时间都不会花费在最终成为瓶颈的代码上。尤其是当您使用现有引擎时,渲染和物理系统中非常昂贵的内循环东西在很大程度上由您掌控。那时,您在游戏脚本中的工作基本上是保持引擎的状态-只要您不向其中扔扳手,那么您可能会在第一次构建时就表现出不错的效果。
因此,除了一点点代码卫生和预算外(例如,如果您可以轻松地重复使用它们,请不要重复搜索/构造内容,保持适度的寻路/物理查询或GPU回读等),养成永不结束的习惯-在我们知道真正的问题出在哪里之前进行优化对提高生产力有好处-避免浪费时间优化错误的东西,并使我们的代码更简单,更容易进行总体调整。
注意:此答案开始时是对DMGregory答案的评论,因此不会重复他提出的非常好的观点。
“在最后改变游戏的某些核心结构,而不是在考虑性能的情况下首次开发它们,是否会变得异常困难?”
对我来说,这就是问题的症结所在。
创建原始设计时,应尝试在最高效率上进行设计。这不是最优化,而是更多关于结构。
示例:
您需要创建一个过河系统。显而易见的设计是桥梁还是轮渡,那么您选择哪个呢?
答案当然取决于人行横道的大小和交通量。这不是优化,而是从适合您问题的设计开始。
呈现设计选择时,您会选择最适合您想要做的事情。
因此,可以说我们的交通量很低,因此我们决定建造两个码头并购买渡轮来处理交通。一个不错的简单实现
不幸的是,一旦启动并运行它,我们发现它看到的流量比预期的要多。我们需要优化轮渡!(因为它可行,现在建造一座桥不是一个好计划)
选项:
在这里,您应该尝试使原始设计尽可能地模块化。
以上所有都是可能的优化,您甚至可以全部执行这三个操作。
但是,如何在不进行大的结构更改的情况下进行这些更改?
如果您具有带有明确定义的接口的模块化设计,那么实现这些更改应该很简单。
如果您的代码未紧密耦合,则对模块的更改不会影响周围的结构。
让我们来看看增加一条渡轮。
一个“坏”程序可能是围绕单个轮渡的想法构建的,并且将码头状态,轮渡状态和位置都捆绑在一起并共享状态。这将很难修改以允许将额外的渡轮添加到系统中。
更好的设计是将码头和渡轮作为单独的实体。它们之间没有紧密的耦合,但是它们有一个接口,轮渡可以到达那里,卸下乘客,接上新乘客,然后离开。码头和轮渡仅共享此接口,在这种情况下,通过添加第二条轮渡,可以轻松更改系统。码头并不在乎实际上有什么渡轮,它所关心的只是某事(任何东西)正在使用其接口。
tl; dr:
然后,您可以在需要优化时更改每个模块内的机制,而无需重新构建整个代码库。
IRiverCrossing
,当很明显流量对于渡轮设置而言太大时,请将桥实现为IRiverCrossing
并放下。当然,诀窍是减少渡轮的外部API和桥接到通用功能,以便它们可以由同一接口表示。
“不及早优化”并不意味着“选择最糟糕的处理方式”。您仍然需要考虑性能的影响(除非您只是在制作原型)。关键不是要在开发时破坏其他更重要的事情,例如灵活性,可靠性等。选择简单,安全的优化方法-选择您要限制的事情以及您可以自由使用的事情;跟踪成本。您应该使用强类型吗?大多数游戏运行良好,并且运行良好。如果您发现Gamemplay灵活性的有趣用途,您将花费多少钱才能删除?
修改优化的代码,尤其是“智能”代码,要困难得多。始终选择它会使某些事情变得更好,而另一些事情则变得更糟(例如,您可能将CPU时间用于内存使用)。在做出选择时,您需要意识到所有的含义-它们可能是灾难性的,但也可能会有所帮助。
例如,指挥官Keen,Wolfenstein和Doom均建立在优化的渲染引擎之上。每个人都有使游戏最初存在的“技巧”(每个人还随着时间的推移开发了进一步的优化功能,但这并不重要)。这很好。充分优化游戏的核心是可以的,这种想法使游戏成为可能。尤其是当您要探索新的领域时,该特定的优化功能使您可以考虑没有太多探索的游戏设计。优化引入的限制可能也会给您带来有趣的游戏玩法(例如,RTS游戏中的单位数量限制可能已经开始作为提高性能的一种方式,但它们也具有游戏玩法效果)。
但请注意,在每个示例中,如果没有优化,游戏就不会存在。他们不是从“完全优化”的引擎开始的,而是从根本上开始,然后逐步发展。他们正在开发新技术,并使用它们来制作有趣的游戏。引擎技巧仅限于代码库的尽可能少的部分-仅在游戏玩法大部分完成时或在允许有趣的新功能出现的地方才引入较重的优化。
现在考虑您可能想要制作的游戏。真的有一些创造或打破游戏的技术奇迹吗?也许您正在设想在无限世界上进行开放世界的游戏。那真的是游戏的核心吗?没有它,游戏将无法正常工作吗?也许您正在考虑一款地形可以无限变形,具有逼真的地质学的游戏;您可以使其在较小的范围内工作吗?它可以在2D而不是3D中工作吗?尽早获得乐趣-即使优化可能需要您重做大量现有代码,这也值得;您甚至可能意识到做大事情并不能真正使游戏变得更好。
作为最近进行大量优化的游戏的示例,我将指出Factorio。传送带是游戏的关键部分-传送带成千上万条,并且它们在工厂各处都承载着许多单独的材料。游戏是从优化过的皮带引擎开始的吗?没有!实际上,最初的皮带设计几乎不可能进行优化-对皮带上的物品进行物理模拟,从而创建了一些您可以做的有趣的事情(这是获得“紧急”游戏的方式-令人惊讶的游戏方式设计师),但是这意味着您必须模拟皮带上的每个项目。有了成千上万个皮带,您就可以得到数以万计的物理模拟物品-即使只是将其卸下并让皮带完成工作,也可以使相关的CPU时间减少95-99%,即使不考虑内存局部性。但是,只有在实际达到这些限制时,这样做才有用。
与皮带有关的几乎所有东西都必须重新制作,以使皮带得到优化。而且需要优化皮带,因为大型工厂需要大量皮带,而大型工厂是游戏的吸引力之一。毕竟,如果您不能拥有大型工厂,那么为什么会有一个无限的世界?有趣的是,您应该问-早期版本没有 :)游戏经过多次重新设计和重塑,以达到现在的状态-包括当他们意识到Java不是开发Java的必经之路时,进行100%彻底的重制。像这样的游戏并切换到C ++。它对Factorio很好用(尽管这仍然是一件好事,但从一开始就没有对其进行优化-特别是因为这是一个业余项目,否则可能会因为缺乏兴趣而失败)。
但事实是,有有有限范围的工厂可以做很多事情-许多游戏都表明了这一点。极限比自由更能赋予人们乐趣。如果“地图”是无限的,Spacechem会更有趣吗?如果您从高度优化的“皮带”开始,那么您将被迫采取这种方式。而且您无法探索其他设计方向(例如查看使用物理模拟的传送带可以做的有趣的事情)。您正在限制潜在的设计空间。似乎不是这样,因为您看不到很多未完成的游戏,但是困难的部分是正确获得乐趣-对于您看到的每个有趣的游戏,可能有数百个游戏无法到达那里并被废弃(或更糟的是,以可怕的混乱状态发布)。如果优化可以帮助您做到这一点-请继续。如果没有... 这可能为时过早。如果您认为某些游戏机制效果不错,但需要进行优化才能真正发挥作用-请继续。如果您没有有趣的机制,不要优化他们。首先找到乐趣-您会发现大多数优化都无济于事,而且常常是不利的。
最后,您将获得一个很棒的有趣游戏。现在进行优化有意义吗?哈!仍然没有您想象的那么清楚。有什么好玩的吗你可以代替吗?不要忘记您的时间仍然有限。一切都需要付出努力,而您想将精力集中在最重要的地方。是的,即使您正在制作“免费游戏”或“开源”游戏。观看游戏的玩法;注意性能成为瓶颈。优化这些地方是否会带来更多乐趣(例如建造更大,更纠结的工厂)?它是否可以吸引更多玩家(例如,在性能较弱的计算机上或在不同的平台上)?您始终需要确定优先级-寻找产出率。您可能会发现,仅从玩游戏和观看其他人玩游戏中就可以找到很多低落的果实。但是请注意重要的部分-要到达那里,您需要游戏。集中精力。
作为最佳选择,请考虑优化永无止境。您完成并转移到其他任务并不是一个带有少许勾号的任务。您总是可以进行“更多优化”,并且任何开发的很大一部分都是要了解优先级。您不是出于优化目的而进行优化-这样做是为了实现特定的目标(例如,“在333 MHz奔腾上同时显示200个单位”是一个很好的目标)。不要仅仅因为您过多地专注于中间目标而无法跟踪最终目标,而中间目标甚至可能不再是最终目标的先决条件。
许多答案似乎都集中在“优化”的性能方面,而我本人也希望从更抽象的角度着眼于优化以及过早进行优化的整个过程。
当我尝试在多氨基酸的帮助下阐述自己的观点时,请为我幽默。
假设我们使用的框架或引擎设置了一些固定的边界。
然后,我们像这样继续创建游戏的第一层/模块。
继续前进,我们构建了第二层/模块。
在这一点上,我们可能会注意到两个模块之间有一些空间,我们可能会尝试对其进行优化以充分利用分配给我们的边界。
完美,现在该应用程序正在充分利用我们可用的资源,该应用程序更好,对吧?
我们继续构建应用程序的第三层/模块,然后突然意识到(也许甚至在最初的计划中我们无法预见的)第三层/模块对我们不起作用。
我们寻找一个替代方案,找到一个替代方案,最后,这还要求我们更改应用程序的第二个模块,因为它与新选择的第三个模块不兼容。(非常感谢它与我们的第一个模块兼容,因此我们不必从头开始重写所有内容。)
所以我们把它们放在一起...
嗯,你能看到发生了什么吗?
通过过早地优化,我们现在实际上使事情变得更糟,因为我们所针对的不是最终的结果。
而且,如果我们以后想添加一些其他模块或额外的花絮,那么此时我们可能不再具有执行这些操作的能力。
调整我们系统的最低级别已不再可行,因为它已被埋在所有其他层之下。
但是,如果我们选择等待我们立即对其进行优化的冲动,那么最终结果将是这样的:
现在,如果我们在这一点上进行优化,我们将得到满意的结果。
希望至少你们中的一些人阅读本书的乐趣与我阅读本书的乐趣一样:),如果您现在觉得自己对这个主题有了更好的了解,那就更好了。
钱。
归结为这一点。钱。由于时间就是金钱*,因此您在无法保证产生更多金钱的活动上花费的时间越多(即您不能将这些活动视为投资)),您浪费的金钱就越多,您赚到的钱就越少游戏。
以下是过早优化的一些潜在副作用,以及应避免进行优化的原因:
*其他答案已充分突出了“时间”部分
作为附带说明,通常,经验有助于确定什么是过早优化,什么不是过早优化,什么对企业有价值,哪些不对企业有价值。
例如,如果您从事游戏A,并在项目结束时意识到XYZ功能在您的游戏循环中特别繁重,那么您最终就开始研究具有完全相同功能的游戏B,从而决定重写功能,并从一开始就优化它是不是真的过早的优化,因为你知道这将是一个瓶颈,如果不采取任何措施。
从定义上讲,优化是将解决方案的效率提高到失去功效的过程。然后,此过程意味着减少了解决方案的空间。
在软件开发的早期阶段,仍然存在“隐藏需求”:如果您减少解决方案的空间过多,您可能最终会遇到无法弹出“隐藏需求”的情况在开发的后期,迫使您修改体系结构,以这种方式增加不稳定性和可能的不良行为。
然后的想法是使整个解决方案正常工作,然后,当所有要求都已确定并实现时,收紧代码。然后您将看到,由于与较新需求的交互作用,您原本会在编码时轻松实现的许多优化现在不再可行。
解决方案的实际空间总是比我们开始时期望的空间大,因为我们无法完全了解非常复杂的系统。
首先使它工作。然后收紧绳索。
简而言之,如果您想稍后进行更改,那么尽早进行优化常常会浪费很多精力,然后您发现您已经将易于更改的代码结构优化为更低的层次,现在您需要将其重新更改为较高的层次。级方法,并再次优化结果。
对于喜欢专注于从“完成有用的工作”(例如优化)中获得乐趣的新手开发人员而言,这是一个常见的错误,他们没有考虑是否是适当的时机。当您获得对大型项目(例如游戏)进行编程的经验时,您将了解何时可以保证优化现有代码库以及何时进行优化。不要害怕犯这个错误,您只会从中学到东西而受益。
通常,只有当您确实不能立即进行开发时,才进行优化。就像您每秒创建和删除数百万个对象60次一样。
因此,就学习经验而言,尽早优化几次是很好的:p
优化的重点是使计算机在代码上运行最佳,而开发则需要使程序员在代码上运行最佳。
优化并不会带来见解。它使计算机工作更少,而程序员更多。
当然,有不同的类别,例如设计汽车。在构建第一个底盘之前优化引擎没什么问题,但是在不知道引擎形状之前优化底盘可能会浪费时间。模块化和规格可以在很多地方进行优化工作,甚至在整个产品组装之前。
我强烈反对所有不应在早期阶段对代码进行优化的声明。这取决于您处于哪个阶段。如果这是MVP-原型,而您只是想看一下需要1-2周的游戏,那么您就可以重写已经编写的所有内容。是的,优化并不重要。但是,如果您已经在开发要发布的游戏,那么它应该一直在优化代码。人们谈论新功能和可以添加的内容的故事是误解。这是设计不良的代码体系结构,而不是优化的代码。如果您的代码设计不错,则无需重写任何内容即可添加新元素。
例如,如果您有A *寻路算法,以最佳方式编写它会更好吗?而不是稍后更改您的一半代码,因为您必须对算法进行某些更改,因为现在它需要另一个方法调用和回调?如果您从一开始就已经有了优化的代码,它将为您节省大量时间。因为您可以绘制对象之间的关系以及它们之间的交互方式,而不是盲目地制作大量新脚本并立即建立所有连接,所以这会导致模糊的sphagetti代码。
我要添加的最后一件事是,即使您不想再制作游戏了,您也可以使用优化的A *算法,将其用于下一个游戏。可重用性。例如,许多游戏具有清单,寻路,程序生成,npc之间的交互,战斗系统,AI,界面交互,输入管理。现在您应该问自己-“我从头开始重写了那些元素几次?” 他们占比赛的70-80%。您真的需要一直重写它们吗?如果未对它们进行优化怎么办?