通常,我应该多长时间和何时优化一次代码?


13

在“正常”的业务编程中,优化步骤通常要等到真正需要时再进行。意味着直到真正需要它之前,您都不应该进行优化。

记住唐纳德·克努斯(Donald Knuth)所说的:“我们应该忘记效率低下,大约97%的时间说:过早的优化是万恶之源”

是时候进行优化以确保我没有浪费时间了。我应该在方法层面上做吗?班级?模块级别?

另外,我应该如何衡量优化?虫?影格速率?总时间?

Answers:


18

在我工作过的地方,我们总是使用多个级别的配置文件。如果您发现问题,则只需将列表再移几下,直到弄清楚发生了什么:

  • “人类分析器”,也就是玩游戏;偶尔感到缓慢或“发痒”吗?注意到生涩的动画?(作为开发人员,请注意,您将对某些类型的性能问题更加敏感,而对其他性能问题则无动于衷。请相应地计划额外的测试。)
  • 打开FPS显示屏,这是滑动窗口5秒的平均FPS。计算和显示的开销很小。
  • 打开配置文件栏,这些文件栏是一系列四边形(ROYGBIV颜色),它们代表每个部分的代码,使用简单的“秒表”计时器来代表帧的不同部分(例如,vblank,预帧,更新,碰撞,渲染,后帧) 。为了强调我们想要的内容,我们将一个屏幕宽度的条形图设置为代表60Hz目标帧,因此,很容易看出您是否处于预算不足50%(仅半条形)或50%以上(该条将缠绕并变成一个半条)。判断大部分帧通常吃什么也很容易:红色=渲染,黄色=更新等。
  • 构建一个特殊的,有工具的构建,在每个函数周围插入“秒表”之类的代码。(请注意,这样做可能会带来巨大的性能,dcache和icache命中率,因此绝对是侵入性的。但是,如果您缺乏适当的采样分析器或对CPU的适当支持,这是可以接受的选择。您也可以很聪明关于在函数输入/退出上记录最少的数据并稍后重建调用跟踪。)在构建函数时,我们模仿了gprof的大部分输出格式。
  • 最重要的是,运行采样分析器;VTune和CodeAnalyst可用于x86和x64,您拥有各种可能在此处提供数据的仿真或仿真环境。

(过去一年的GDC中有一个有趣的故事,一个图形程序员,他为自己拍摄了四张照片-快乐,冷漠,恼火和生气-并根据帧速率在内部版本的一角显示了适当的图片。内容创建者很快就学会了不要为所有对象和环境打开复杂的着色器:它们会让程序员感到生气。请注意反馈的力量。)

请注意,您还可以做一些有趣的事情,例如连续绘制“轮廓栏”,以便看到尖峰图案(“每7帧丢失一帧”)等。

不过,直接回答您的问题是:根据我的经验,虽然很诱人(而且常常是有收获的-我通常会学到一些东西)重写单个功能/模块以优化指令数量或icache或dcache性能,但实际上我们确实需要做这有时在我们遇到特别令人讨厌的性能问题时,我们定期处理的绝大多数性能问题都归结为设计。例如:

  • 我们应该缓存在RAM中还是从磁盘重新加载播放器的“攻击”状态动画帧?每个敌人呢?我们没有RAM来完成所有操作,但是磁盘负载非常昂贵!如果同时弹出5或6个不同的敌人,您会看到拴系!(好吧,交错产卵怎么样?)
  • 我们是对所有粒子执行单一类型的操作,还是对单个粒子执行所有操作?(这是一个icache / dcache的折衷方案,答案并不总是很清楚。)如何将所有粒子拆开并将位置存储在一起(著名的“数组结构”)与将所有粒子数据保存在一个位置(“结构数组”)。

您会听到它,直到它在任何大学水平的计算机科学课程中变得令人讨厌为止,但是:它实际上是关于数据结构和算法的。花一些时间在算法和数据流设计上通常会给您带来更多实惠。(请确保您已经阅读了Sony开发人员服务人员的出色的《面向对象编程的陷阱》幻灯片,以获取一些见解。)这通常是花费在白板或UML工具或创建许多原型上的时间,而不是使当前代码运行得更快。但这通常更值得。

另一个有用的启发式方法:如果您接近引擎的“核心”,则可能需要进行一些额外的工作和尝试来进行优化(例如,对那些矩阵乘法进行矢量化处理!)。距离核心越远,除非您的配置工具之一告诉您其他情况,否则您不必担心太多。


6
  1. 预先使用正确的数据结构和算法。
  2. 在进行概要分析并确切知道热点位置之前,请不要进行微优化。
  3. 不用担心变得聪明。编译器已经完成了您正在考虑的所有小技巧(“哦!我需要乘以四!我将左移两位!”)
  4. 注意缓存未命中。

1
在某种程度上,仅依靠编译器是明智的。是的,它将执行一些您不会想到的窥孔优化(并且如果没有汇编就无法完成),但是对于算法应该执行的操作却一无所知,因此它无法进行智能优化。另外,如果您知道自己在做什么,那么通过在汇编或内部函数中实现关键代码可以赢得多少周期,您会感到惊讶。编译器并不像他们想象的那样聪明,除非您在任何地方都明确告诉他们(例如,虔诚地使用“限制”),否则他们不知道您要做什么。
Kaj 2010年

1
再次,我必须评论一下,如果您仅查找热点,则会错过很多周期,因为您不会发现任何全面陷入的周期(例如,智能指针...。在任何地方都进行引用,永远不会出现作为一个热点,因为实际上您的整个程序都是一个热点)。
Kaj 2010年

1
我同意您的两个观点,但我将其中大部分归结为“使用正确的数据结构和算法”。如果您到处都经过引用计数的智能指针,并且正在通过计数浪费循环,那么您肯定选择了错误的数据结构。
优厚

5

也要记住“过早的悲观化”。尽管无需在每一行代码上都做硬核,但有理由意识到您实际上正在开发一款具有实时性能影响的游戏。
尽管每个人都告诉您测量和优化热点,但该技术不会向您显示隐藏区域中丢失的性能。例如,如果代码中的每个“ +”操作都需要花费两倍的时间,则它不会显示为热点,因此您将永远不会对其进行优化甚至实现,但是由于它在整个环境中都在使用放置它可能会花费很多性能。您会感到惊讶的是,其中有多少循环没有被发现而滴落而下。因此,请注意您的工作。
除此之外,我倾向于定期进行分析,以了解其中的内容以及每帧剩余的时间。对我而言,每帧时间是最合乎逻辑的,因为它可以直接告诉我帧率目标的位置。还尝试找出峰值在哪里以及导致峰值的原因-我更喜欢稳定的帧速率而不是带有尖峰的高帧速率。


这对我来说似乎太不对了。当然,每次调用我的“ +”可能要花费两倍的时间,但这实际上只在紧紧的循环中起作用。在紧密循环内,更改单个“ +”的作用要比在循环外更改“ +”的作用大几个数量级。当可以节省毫秒时,为什么还要考虑十分之一微秒呢?
野鸭

1
那么您将不了解滴流损失背后的想法。“ +”(仅作为示例)每帧被称为数十万次,而不仅仅是紧密循环。如果每次您都损失了很多周期,那么这会损失几个周期,但是由于调用在您的代码库/执行路径中是均匀分布的,因此它永远不会显示为热点。因此,您所讲的不是十分之一微秒,而是实际上是十分之一微秒的数千倍,总和为数毫秒。追求低垂的果实(紧紧的圈)后,我以这种方式获得了毫秒以上的收获。
Kaj 2010年

就像滴水的水龙头。为什么要担心节省一点钱呢?-“如果您的水龙头以每秒一滴的速度滴水,则每年可能浪费2700加仑。”
Kaj 2010年

哦,我想还不清楚我的意思是当operator +重载时,它会影响代码中的每个“ +”-您确实不想优化代码中的每个“ +”。我猜这是一个不好的例子。。。我的意思是,它是“核心功能的示例,在实现可能比预期慢的所有地方都被调用,尤其是在运算符重载或其他混淆的C ++结构隐藏时”。
Kaj 2010年

3

一旦准备好发布游戏(最终版或Beta版),或者游戏明显变慢,那便是配置应用程序的最佳时间。当然,您始终可以随时运行分析器。但是,是的,过早的优化是万恶之源。毫无根据的优化;您需要实际数据来证明某些代码运行缓慢,然后再尝试“优化”它。探查器为您执行此操作。

如果您不了解分析器,请学习它!这是一篇很好的博客文章,展示了探查器的实用性。

大多数游戏代码优化都归结为减少每帧所需的CPU周期。一种方法是在编写每个例程时对其进行优化,并确保其速度尽可能快。但是,有句俗话说:90%的CPU周期花费在10%的代码中。这意味着将所有优化工作定向到这些瓶颈例程将具有统一优化所有事物的效果的十倍。那么,您如何识别这些例程?分析使它变得容易。

否则,即使您的小型游戏中算法效率不高,但它仍以200 FPS的速度运行,您真的有理由进行优化吗?您应该对目标计算机的规格有一个很好的了解,并确保游戏在该计算机上运行良好,但是(可能会)浪费时间,可以将更多的时间浪费在编写或改进游戏上。


虽然低挂果确实倾向于占代码的10%,并且很容易被概要分析所捕获,但仅通过概要分析来进行工作将使您错过很多被称为但很少的例程各自都有一些错误的代码-它们不会显示在您的个人资料中,但每次调用都会浪费很多周期。它真的加起来。
Kaj 2010年

@Kaj,好的分析器会汇总不良算法的数百个单独执行的所有结果,并向您显示总数。接下来,您会说:“但是,如果您有10种错误的方法,而所有这些方法的调用频率都是1/10,该怎么办?” 如果您将所有时间都花在这10种方法上,那么您将失去所有悬而未决的成果,在这些方面您将获得更大的收益。
约翰·麦当劳

2

我发现构建剖析很有用。即使您没有积极优化,也要对在任何给定时间限制性能的想法有所了解。许多游戏都有某种可叠加的HUD,该HUD显示一个简单的图形图表(通常只是一个彩色条),显示游戏循环各部分占用多帧的时间。

将性能分析和优化推迟到很晚才是一个坏主意。如果您已经制作了游戏,并且超出了CPU预算200%,并且无法通过优化发现这一点,那么您就搞砸了。

在编写时,您需要了解图形,物理等方面的预算。如果您不知道自己的表现如何,就无法做到这一点,并且在不知道自己的表现如何以及可能会有多少懈怠的情况下无法猜测。

因此,从第一天开始就建立一些性能统计数据。

至于什么时候处理问题-再次,最好不要将它留得太晚,以免您不得不重构一半的引擎。另一方面,如果您认为明天可能会完全更改算法,或者没有将真实的游戏数据放入其中,则不要太忙于优化内容以免挤每个周期。

随手捡起低垂的果实,定期处理大的东西,你应该没事。


要添加到游戏分析器中(我完全同意),扩展游戏分析器以显示多个条(针对多个帧)可以帮助您将游戏行为与峰值相关联,并可以帮助您发现平均捕获中不会出现的瓶颈与探查器。
Kaj 2010年

2

如果在上下文中查看Knuth的报价,他会继续解释,我们应该使用分析器之类的工具进行优化。

在构建了非常基本的体系结构之后,您应该不断地对应用程序进行概要分析和内存概要分析。

分析不仅可以帮助您提高速度,还可以帮助您发现错误。如果您的程序突然急剧改变速度,通常是由于存在错误。如果您不进行分析,则可能不会引起注意。

优化的技巧是通过设计实现。不要等到最后一分钟。确保程序的设计能够为您提供所需的性能,而无需真正的讨厌的内循环技巧。


1

对于我的项目,我通常在基本引擎中应用一些非常需要的优化。例如,我始终喜欢使用SSE2和3DNow来实现良好的可靠SIMD实现!这样可以确保我的浮点数学运算可以随心所欲地进行。另一个好的做法是在编写代码时养成优化的习惯,而不是退缩。在大多数情况下,这些小技巧与您编写的代码一样耗时。在对功能进行编码之前,请确保您研究了最有效的方法。

在我看来,最重要的是,它的困难在于使您的代码在已经烂透之后更加高效。


0

我想说,最简单的方法就是利用您的常识-如果某些东西看起来运行缓慢,那么请看一下。看看这是否是瓶颈。
使用探查器可以查看函数的速度以及调用它们的频率。
优化或花费时间尝试优化不需要的东西绝对没有意义。


0

如果您的代码运行缓慢,请运行探查器,查看究竟是什么导致其运行缓慢。或者,您可能会很主动并且已经开始运行探查器,然后才开始注意到性能问题。

当帧速率下降到游戏开始遭受损失的时候,您将需要进行优化。最可能的罪魁祸首是您的CPU消耗过多(100%)。


我会说GPU与CPU一样可能。确实,取决于事物之间紧密耦合的程度,完全有可能在一半的帧中将大量CPU约束在CPU中,而将GPU的另一半约束在很大程度上。哑巴分析甚至显示出两者的利用率均不到100%。确保您的配置文件足够细粒度以显示该纹理(但又不能如此细粒度以至于不会引起干扰!)
JasonD 2010年

0

您应该根据需要优化代码。

我过去所做的只是在启用概要分析的情况下持续运行游戏(至少始终在屏幕上显示帧率计数器)。如果游戏变慢(例如,低于最低规格计算机上的目标帧速率),请打开探查器,查看是否有热点出现。

有时不是代码。我过去遇到的许多问题都是面向GPU的(当然,这是在iPhone上)。填充率问题,绘制调用过多,没有批处理足够的几何图形,低效的着色器...

除了解决棘手问题(例如寻路,物理)的效率低下的算法外,我很少遇到代码本身是罪魁祸首的问题。那些棘手的问题应该是您花费大量精力来使算法正确,而不用担心较小的事情。


0

对我而言,最好遵循良好的准备好的数据模型。并在优化之前迈出重要的一步。我的意思是在开始实施一些重大的新事物之前。优化的其他原因是当我失去对资源的控制时,App需要大量的CPU负载/ GPU负载或内存,而我不知道为什么:)或太多。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.