Answers:
与关于“什么是唯一的真实路径”的所有其他问题一样,这些都是工具箱中的所有工具,在某些情况下,big-O胜过一切,而在任何地方都无关紧要(tm)。
您将“从不”编写物理求解器而无需担心big-O。如果不考虑排序算法,就不会实现排序算法(对于除最小数据集之外的任何数据集)。如果您正在编写网络游戏,则将需要关注每位用户的性能和网络流量扩展方式。
您可能不太关心big-O,但我确实无法想到某个时间,但是我敢肯定有一些。:)幸运的是,我们在游戏中所做的大多数事情都是线性扩展的;您想从光盘读取文件吗?这将花费与文件大小成线性比例的时间(不考虑寻找扇区大小的可能因素和可能造成的后果)。
但是,如果要在实体列表中找到特定实体怎么办?每次执行时都是线性搜索。如果您需要为世界上的每个实体找到一次玩家,那么这种方法将杀死您(除了最琐碎的游戏之外的所有游戏),即使这样,也有可能值得“优化”此查找以保持恒定的时间(例如,存储索引)或指向某个位置的玩家的指针),让您有更多的时间去做玩家实际可以看到的其他事情。
我想可以总结一下。每当处理器执行无法直接为播放器代表的操作时,都是在浪费时间。最大化处理器正在处理将显示给播放器的数据的时间,将最大化WOW!你在给玩家。
大O在大多数情况下都很重要,但有时理论上看似“更糟”的算法在实践中却要快得多。
看看Tony Albrecht的一个很好的例子:http : //seven-degrees-of-freedom.blogspot.com/2010/07/question-of-sorts.html
在游戏开发中到处都是这样,在这种情况下,操作中的项目数量很大,以至于一个非常不同的算法更快;或者很小,以至于一个笨拙的算法就足够了(或者适合缓存,因此它会覆盖效率)在的更好的算法)。
Big O的问题在于,它是任务复杂性的通用名称,没有考虑到现代目标硬件的复杂性,也没有提供关于设置时间开销的任何见解。
在许多情况下,最佳的最佳解决方案是两步。在实践中,游戏开发人员倾向于采用低O算法,但要在开发或调试时间上与成本保持平衡。找到合理的解决方案后,您始终必须查看硬件如何处理任务,以及如何让硬件在更短的时间内完成更多工作。
当我在引擎正在编码,我经常只关注一个固定的n
:我已经有了一个空间分区限制对象的接收数量update()
,physics()
以及render()
大约那些在屏幕上和周边地区。通常,每场游戏的最大批处理量是非常明确的,尽管它总是比您计划的要大一些。
在这种情况下,我不太关心big-O,而是关心常数因子乘数和低阶项。对于具有a*n^2 + b*n + c
()这样的运行时的函数O(n^2)
,我通常更关心减少a
和可能消除c
。设置或拆卸成本c
可能成比例地变大或变小n
。
但是,这并不是说big-O(或更特别是big-theta)是很好的代码气味指示器。看到某个O(n^4)
地方,或更糟的是O(k^n)
几何时间,是时候确保您正在考虑其他选择。
我通常更关心big-O的最优性,并且在处理数据制作工具时会越过障碍寻找big-O较低的算法。虽然通常在给定关卡/流区域中的对象数量是明确定义的,但整个游戏中的对象/艺术品资源/配置文件/等的总数可能不是。这个数字也大很多。即使运行并行数据制作,我们仍然需要等待一分钟左右的时间(我知道,呜呜的叫声-控制台的数据制作可能需要几个小时-我们大多是小型手持游戏机)才能经历一个jam data-clean && jam data
周期。
举一个具体的例子:使用背景图元流算法来流式传输8x8 256色图块,这真的是一发不可收拾。在后台“层”之间共享流缓冲区非常有用,并且在给定级别中,我们最多可以有6个共享同一缓冲区。问题在于,根据所有6层的可能位置来估计所需缓冲区的大小-如果它们是质数宽度/高度/滚动率,则您很快就开始进行详尽的搜索-开始接近O(6^numTiles)
-在许多情况下属于“比宇宙存在更长的时间”类别。幸运的是,大多数情况下只有2-3层,但即便如此,我们的运行时间仍超过半小时。目前,我们对这些可能性的很小一部分进行了采样,增加了粒度,直到经过了一定的时间(或者我们已经完成了任务,对于小型双层配置而言,可能会发生)。我们会根据先前被证明是错误的频率的先前统计数据来稍微提高此估算值,然后添加一些额外的填充以达到良好的效果。
另一个有趣的例子:在前一段时间的PC游戏中,首席工程师用跳过列表进行了一段时间的实验。内存开销最终导致更多的缓存效果,这给整个事务增加了一种非恒定的乘数-因此,对于small而言,它们根本不是一个好选择n
。但是对于频繁搜索的较大排序列表,它们可以提供好处。
(我经常发现,朴素算法的big-O较高,在较小的数据集上更快,并且更易于理解;更有趣/更复杂的算法(例如patricia trie)对于人们来说更难于理解和维护,但是在较大的数据集上则具有更高的性能。数据集。)
它可能很方便,但也可能无关紧要。以我最近的游戏为例,它是Smash TV的克隆版本。自上而下的游戏,怪物从侧面倒入,然后射击它们。
现在,有很多聪明的方法可以确定碰撞。您可以使用KDtrees划分空间,这样就不会针对无法击中的怪物测试子弹。而且,可以肯定的是,我本可以很聪明,但我可以那样做。
但是我感到很懒惰,所以我只是将每个子弹与每个怪物进行比较。即使在最忙碌的情况下,以60fps的速度运行时,碰撞代码仍远远不到游戏CPU的10%。Big-O:无关紧要。
同样,我有一个4x风格的游戏,您在岛上建造城市,有时城市被摧毁。我本可以很聪明,然后试图从收入变量中减去被摧毁城市的收入。但是我没有。每当发生任何变化时,我都会抹去收入,并从头开始重新计算。与CPU完全无关。
Big-O在游戏中和其他所有方面一样重要:也就是说,直到它变得至关重要之前,它都完全不重要。
去写一些代码。如果速度太慢,则对其进行分析。
Big-O分析很重要,但这并不是游戏开发中首先要考虑的问题。由于制作游戏涉及许多复杂的代码,因此我始终建议将“代码简单性”作为算法的首要条件。具有复杂簿记的算法只会浪费您的时间。
我认为在开发过程中游戏始终以60 fps运行非常重要。当您低于此值时,要做的第一件事就是运行探查器。一旦发现瓶颈,就可以对其进行攻击。很多时候,您需要做一些非编码的工作,例如告诉关卡设计师在一个区域中放置更少的东西(并为他们提供工具)。
有时,您实际上会确定一些需要加速的代码。我觉得这是有趣的工程!我希望我有更多机会这样做。当然,您想一次迭代更改一件事情并衡量性能。我发现的典型问题是:
Big-O只是一个准则-可以告诉您可以从算法获得的粗略性能- 以及随着数据集大小的增加如何扩展性能。您必须记住关于Big-O的两件事:
1)如果您有两种算法大多数都做相同的事情,但是其中一种算法的O更好,那么您可能应该选择那种算法(显然)
2)大O与渐近分析有关。只有当n大时, Big-O才真正起作用。例如,对于小n,O(n)算法的性能可能与O(n ^ 2)one ...非常相似。如果您要谈论的算法是每个顶点需要n ^ 2个操作,但是n = 2或n = 3,那么就没有为O(n ^ 2)算法相差无几(以4个9 OPS RESP)和一个O(n)一个(分别为2和3个操作)。但是,如果n = 9,那么您突然谈论的是O(n ^ 2)算法的81个操作,而O(n)的算法只有9个操作-差异更大-如果n = 100,则您是谈论100个操作数与10000个操作数-差异更大。
因此,您必须始终从这种角度考虑Big-O:这是为了比较当n变大时基于最坏情况的性能 执行相同操作的算法。当n非常小时,算法之间的差异几乎可以忽略不计。
在为游戏功能或游戏某个方面制作原型时,您根本不必担心对其进行优化。
在对其进行原型设计和了解该功能的特性的过程中,必要的优化将变得显而易见,并将在大多数情况下将其纳入最终设计中,例如第二自然。
不要流汗。
在游戏(和其他大多数游戏)开发中,我们抱怨每个循环执行一个额外的操作:
for (int i = 0; i < array.length; i ++) { /* ... */ }
与
for (int i = 0, l = array.length; i < l; i ++) { /* ... */ }
大多数现代游戏都有物理学,您会发现n体模拟问题。在朴素的算法中,它是O(n ^ 2),但是有一个优化使它成为O(n log n)(但牺牲了一些准确性)。
您可能会说,您不是在编写重力和粒子相互作用,而是如何使一支(僵尸)军队的团队行为依赖于其他位置(更确切地说是蜂拥而至)移动呢?
在传统的碰撞检测算法中,时间复杂度像n体一样是O(n ^ 2)。但是,有一种更好的方法:将世界分成许多小部分,以便仅检测同一部分内的对象。参见http://www.videotutorialsrock.com/opengl_tutorial/collision_detection/text.php。
如果您的游戏可以编写脚本,请不要让脚本编写者在脚本中编写O(n ^ 2)(及以上)数字运算算法,例如搜索用户的行李袋。而是在代码中创建一个内置函数。
在现实世界中,只有原始性能才重要。现在,算法的Big-O可以作为使用内容的第一个指示,但是根据硬件的不同,实现的效率可能非常低。例如,执行线性搜索通常比二进制搜索更快,因为您可以进行线性内存访问并且没有分支。
此外,由于当前多线程平台和体系结构的发展方向,Big-O失去了很多意义,因为它仅考虑了每个操作的内存或数据触摸的垂直可伸缩性,而不是考虑算法的方式。可以使用更多数量的线程进行扩展。