有两个可能需要优化速度的区域:
- 花费最多时间的地方
- 最被称为的代码
哪个是开始优化的最佳位置?
通常,最常调用的代码执行时间已经很短。您是在优化较慢的,较少被调用的区域还是在优化较快的,频繁使用的区域?
有两个可能需要优化速度的区域:
哪个是开始优化的最佳位置?
通常,最常调用的代码执行时间已经很短。您是在优化较慢的,较少被调用的区域还是在优化较快的,频繁使用的区域?
Answers:
您应该在95%的时间内忽略低效率。首先,使其正常运行,然后进行分析...
您选择的高级算法可能会对软件的整体性能产生巨大影响,以至于一个看似微不足道的选择可能意味着等待程序启动20分钟与拥有快速响应的UI之间的区别。
例如,在3D游戏中:如果从场景图的对象的简单平面列表开始,您将看到相对少量对象的性能极差;但是,如果改为在绘制时实现体积层次结构(如八叉树或BVH)并剔除树的一部分,则会看到性能的大幅提升。
当您的设计看起来正确时,则可以继续进行...
低级算法也会产生重大影响。例如,在进行图像处理时,如果以错误的顺序读取图像,则遇到不断发生的L2高速缓存未命中时,您会遇到大量的速度下降;重新排列您的操作可能意味着性能提高十倍。
此时,分析并找到花费程序大部分时间的地方,并找到消除该时间的方法。
首先,运行一个探查器,找出您的代码在哪里花费时间。
然后,查看那些地方,看看哪些地方最容易优化。
寻找最简单的修复方法,使其首先获得最大的收益(低挂果)。确切地说,不必太担心它的重要性。如果简单,请修复它。它将加起来。25个简单修复程序可能比1个大修复程序更快,并且它们的累积效果可能更大。如果很困难,请记下笔记或提交错误报告,以便稍后进行优先处理。在这一点上,不必太担心“大”或“小”-这样做,直到获得使用很少时间的功能。完成此操作后,您应该更好地了解所发现的其他问题中,哪些可以用最少的时间获得最大的收益。
不要忘了在修补程序之后进行性能分析,以此作为一种回归测试,以验证您的性能变化是否达到了您希望的效果。另外,不要忘记运行您的回归套件,以确保没有功能受损。有时,性能不佳表示存在变通办法,而尝试修复性能会破坏功能。
无法优化但占用大量时间的小功能可能仍在暗示优化位置。为什么要这么多调用该函数?是否有一个函数调用那个不需要使用太多函数的小函数?是重复工作还是进行不必要的工作?在堆栈中查找被调用的时间,直到您确信应该经常调用它为止,并查看是否找到了一个效率较低的算法的较大函数。
编辑添加:由于您的特定功能需要花费很长时间,因此请尝试执行上述步骤,仅将特定功能运行10次左右。
很难说。这实际上取决于代码在做什么。运行性能测试,获取性能配置文件,并查看并了解在各个领域花费了多少实际时间。您的概括是...概括,它因项目而异。
例如,被调用最多的代码可能只是登录到文件或控制台。进行优化没有太多意义,如果已经是一两行代码无法简化的话,那么优化此类内容的任何努力可能都不值得实际编码。最少被调用的代码可能是某些极其复杂的函数中使用的一些庞然大物的查询。该功能可能仅达到在整个执行运行(与10000为简单的日志语句)称为100倍,但如果需要20秒,每次通话时间运行,也许这就是其中的优化应该开始?或者也可能是相反的方式,大查询是最常用的查询,而日志记录语句每100个查询仅调用一个查询...
我通常不必担心这种事情(直到需要进行性能调优),除非我提前知道会发生什么情况。
好吧,“我们”通常不会进行优化,直到出现明显的缓慢需求时才需要进行优化。
而且,当这种需求体现出来时,通常会附带一些关于什么是优化的确切提示。
因此答案是通常的:“取决于情况。”
您应该在几个典型的运行中使用探查器,并查看在代码的每个部分中花费的总时间,无论您到达那里的频率或频率如何。优化这些部件应始终提高速度。
根据实现语言的底层,您还应该找出导致大多数高速缓存未命中的部分。合并调用代码将在这里有所帮助。
问题是短语“花费最多的时间”是模棱两可的。
如果它的意思是“最常在程序计数器中找到的地方”,那么我就看到程序在字符串比较,内存分配,数学库函数上花费最多的时间。换句话说,日常程序员不应该碰到的功能。
如果这意味着“在程序员的代码中执行的语句消耗大量时间”,那么这是一个更有用的概念。
“被调用最多的代码”概念的问题在于,所花费的时间是所调用的频率与每次调用所花费的时间(包括被调用方和I / O)的乘积。由于花费的时间可能会变化几个数量级,因此被调用的次数并不能告诉您问题的严重程度。函数A可以被调用10次并花费0.1秒,而函数B可以被调用1000次并花费一微秒。
有一两件事,会告诉你去哪里看是这样的:只要一行代码导致要花费的时间是在栈上。因此,例如,如果一行代码是热点,或者它是对库函数的调用,或者如果它是30级调用树中的第20条调用,则它占20%的时间,那么它有20%的时间在堆栈上。堆栈中的随机时间样本每个都有20%的机会显示出来。而且,如果可以在I / O期间进行采样,它们将向您显示I / O的原因,这与浪费的CPU周期一样或更多。
而且这完全独立于调用多少次。
我曾经为一家超级计算机供应商做过基准测试和市场营销,所以快速击败竞争对手并不是最重要的事情,这是唯一的事情。这种结果要求您使用良好算法和数据结构的组合,该结构将允许占用大量CPU资源的部分有效地运行峰值。这意味着您必须非常了解计算量最大的操作是什么,以及什么样的数据结构将使它们以最快的速度运行。然后就是围绕那些优化的内核/数据结构构建应用程序。
从更一般的意义上讲,是事实调整之后的典型情况。您分析,查看热点,并且您认为可以加快的热点就是您正在研究的热点。但是这种方法很少会给您带来任何可能的最快实施方案。
但是,从更普遍的角度来看(不存在算法错误),对于现代机器,您必须考虑性能是由三件事决定的:数据访问,数据访问和数据访问!了解有关内存层次结构(寄存器,缓存,TLB,页面等)的信息,并设计数据结构以充分利用它们。通常,这意味着您希望能够在紧凑的内存占用空间内运行循环。如果您只是编写(或提供了)一个应用程序然后尝试对其进行优化,那么您通常会感到数据结构受累,而这些数据结构利用内存层次结构的效率很低,而更改数据结构通常会涉及大量的重构工作,因此经常卡住。
如果您想获得优化工作的回报,则必须查看花费时间最多的代码。我的总体目标是至少花费80%的时间。我通常将目标提高10倍。有时这需要对代码的设计进行重大更改。这种变化使您的运行速度提高了大约四倍。
我一直以来的最佳性能提升是将运行时间从3天减少到9分钟。我优化的代码从3天延长到3分钟。该应用程序取代了该应用程序,将其减少到9秒,但这需要更改语言并进行完全重写。
优化一个已经很快速的应用程序可能会很愚蠢。我仍然会以最多的时间为目标区域。如果您可以花10%的时间立即返回,那么您仍然需要90%的时间。您迅速达到了收益递减的规则。
根据您为规则进行优化的情况,仍然适用。寻找主要的资源用户并对其进行优化。如果您正在优化的资源是系统瓶颈,那么您可能会发现您所做的就是将瓶颈更改为另一个资源。
通常,在大多数情况下,它将是更重要的功能,而不是在循环中最常被调用十亿次的功能。
当您进行基于样本的概要分析(使用工具或手动进行)时,最大的热点通常会出现在进行简单操作的微小叶调用中,例如比较两个整数的函数。
该功能通常不会从很多优化中受益。至少,那些细粒度的热点很少是头等大事。调用该叶函数的函数可能是麻烦的制造者,或者调用该函数的函数调用该函数,例如次优排序算法。使用好的工具,您可以从被叫者深入到呼叫者,还可以查看谁花费最多的时间致电被叫者。
痴迷于被调用者,而不是在性能分析会话中不看呼叫图中的调用者,通常是一个错误,除非您在微观层面上做事效率非常低。否则,您可能过多地流了小东西,却看不到大图景。只是手头有一个探查器并不能防止您沉迷于琐碎的事情。这只是朝着正确方向迈出的第一步。
另外,您还必须确保所进行的操作与用户的实际意愿相吻合,否则,在测量和基准测试中完全受纪律和科学对待就毫无价值,因为它与客户对产品的操作不符。我曾经有一位同事从细分算法中调出地狱,将一个多维数据集细分为十亿个小面,他为此感到非常自豪....除了用户不将简单的6多边形多维数据集细分为一个十亿个面外方面。当它尝试在要细分的100,000个以上的多边形的量产车模型上运行时,整个过程变慢了爬行速度,此时,如果不减慢爬行速度,它甚至无法进行2或3个细分级别。简而言之,他编写的代码针对不切实际的小输入大小进行了超级优化,
您必须根据用户的利益来优化实际用例,否则,它会变得毫无价值,因为所有那些至少在某种程度上降低了代码可维护性的优化都没有多少用户收益,并且对代码库只有负面影响。