优化代码

时间:2008-11-28 08:44:39

标签: performance language-agnostic optimization

您可以使用自己喜欢的语言获得一堆代码,这些代码组合在一起构成了一个相当复杂的应用程序。它运行得相当慢,你的老板要求你优化它。 您最有效地优化代码的步骤是什么?

优化代码时,您发现哪些策略不成功

重写:在什么时候你决定停止优化并说“这是没有完全重写的速度。”在什么情况下你会提倡简单的完全重写?你会如何设计呢?

12 个答案:

答案 0 :(得分:42)

在尝试任何优化之前

配置文件。

10次中的9次,时间不会消耗在你可能猜到的地方。

通常不成功的策略是微优化,当实际需要的是适当的算法时。

强制性的唐纳德克努特引用:

  

“我们应该忘记效率低,大约97%的时间说:过早的优化是所有邪恶的根源”

答案 1 :(得分:23)

步骤:

  1. 测量
  2. 分析
  3. 决定
  4. 实施
  5. 重复
  6. 首先获取一个分析器来测量代码。不要陷入假设你知道瓶颈在哪里的陷阱。即使之后,如果您的假设被证明是正确的,也不要认为您可以在下次执行类似任务时跳过测量步骤。

    然后分析您的发现。查看代码,找出可以做些什么的瓶颈会产生影响。试着估计这将有多大的改进。

    根据您的分析,决定是否要沿着这条路走下去。收益是否值得?是否需要重写?也许你会发现虽然它运行缓慢,但是它会达到或者你已经接近性能曲线的顶部,在那里需要努力寻求微小的改进是不合理的。

    实施您的更改,必要时进行重写,或者重构代码(如果这是您已经失败的路径)。只需很小的步骤就可以轻松测量,如果你的改变给了你想要的东西,或者你需要回溯一步并尝试不同的路线。

    然后回到开始并测量,分析,决定,实施等。

    另外,关于重构代码的说明。您应该改变的第一件事是大算法级别的方法。比如将排序算法替换为性能更好的排序算法等等。不要从行级优化开始,比如如何获得一个增加值的行稍快一点。这些是最后一级优化,通常不值得,除非你在极端性能条件下运行。

答案 2 :(得分:8)

如果没有某些类型的分析,甚至不必费心去尝试任何事情,我不能强调这一点!不幸的是,我所知道的所有剖析器都很糟糕或者很昂贵(不管是商业成本!)所以我会让别人在那里提出建议:)。

当你的数据告诉你需要重写时,你知道你需要重写,这听起来像是递归的,但实际上只是意味着当前架构或软件堆栈的成本本身就足以推动你性能悬崖,所以你在本地更改中做的任何事情都无法解决整体问题。但是,在你拿出File-&gt; New ...命令之前,制定计划,构建原型并测试原型比同一任务的当前系统做得更好:令人惊讶的是,经常没有明显的区别!< / p>

答案 3 :(得分:4)

首先不要忘记这些:

  • 过早优化是所有邪恶的根源
  • 表现来自设计

其次;

请勿尝试查看

我认为这是优化的基本规则,测试它,除非你测试它并证明它工作你不会知道。

在你的情况下,我要做的是,首先重构代码,掌握它。

如果你有单元测试,你很幸运,只需按功能执行功能,并专门检查最常用的代码(使用分析来观察呼叫以及瓶颈在哪里)。如果您没有测试,请编写一些基本测试,在某些条件下确认总体输出,这样您就可以确保不会破坏任何内容并且可以自由地进行实验

答案 4 :(得分:1)

除了每个人提到的分析之外,我总是首先考虑的两个解决方案(经过精心设计)是Memoization和Lazy loading,它们都很容易实现,通常会产生很大的不同。

答案 5 :(得分:1)

所有好的答案。

我会改进建议的“措施”部分。我进行测量的目的是量化我可能做出的任何改进。但是,对于查找需要修复的内容(这是一个不同的目的),我会得到几个调用堆栈样本,手动

假设为了简单起见,程序需要20千兆周期才能运行,而应该需要10千兆周期。 如果我随机暂停10次,那么在其中的5次或多或少的情况下,它将处于其中一个不必要的循环中。我可以通过查看调用堆栈的每一层来查看是否需要循环。如果可以消除堆栈中任何级别的任何调用指令,则不需要循环。如果这样的指令出现在多个堆栈上,消除它加速程序,其数量大约是它所在的堆栈样本的百分比。

任何出现在多个堆栈上的指令都是可疑的 - 堆栈越多,越可疑。现在,call _main可能不是我可以删除的,但是foo.cpp:96 call std::vector::iterator:++
如果它出现在多个堆栈上,绝对是一个关注焦点。

出于风格原因,可能不会想要来替换它,但是人们会大致知道为该选择支付的性能价格。

因此,优化包括识别嫌疑人并找到替换或消除嫌疑人的方法。

困难的部分(我已多次这样做)是理解什么是必要的,什么不是。为此,您将了解如何以及为何在该程序中完成任务,因此如果某些活动是循环猪,您可以知道如何安全地替换它。

一些循环猪可能很容易修复,但你会很快遇到你不知道如何安全更换的那些。为此,您需要更熟悉代码。

如果您能够选择已经参与该计划的人的大脑,那将会有所帮助。

否则(假设代码是~10 ^ 6行,就像我一直在使用的那样)你可以相当容易地获得一些加速,但是要超越它,可能需要数月才能到达你很有意义的地方变化。

答案 6 :(得分:0)

确保有足够的单元测试,以确保您将进行的任何优化都不会破坏任何内容

确保您的执行环境符合资格。有时,执行选项的简单更改可能会有很长的路要走。

然后才开始分析代码。

只有在当前架构可能不支持的未来演进足够的情况下,才应考虑重写决策(对于已经工作代码)。
如果简单的修复可以加速一个不应该发展很多的代码,那么就不需要完全重写。

停止的标准通常是与最终用户(客户)合作确定的,但我会建议正式文档修复目标,以便通过此优化过程实现。

答案 7 :(得分:0)

首先确定您的优化目标是什么 - 为给定硬件平台上的特定操作的时间设置目标。准确地测量性能(确保您的结果是可重复的)并且在类似生产的环境中(没有VM等,除非您在生产中使用它!)。

然后,如果你认为它已经足够快,你可以就此止步。

如果它仍然不够好,那么将需要一些额外的工作 - 这是分析进入的地方。你可能无法很好地使用分析器(例如,如果它影响行为太多),应该使用哪种情况下的仪器。

答案 8 :(得分:0)

假设代码需要优化,那么我会使用: 1.)优化缓冲区的处理方式 - 缓存优化。缓存延迟是一个很大的区域,它会像CPU一样糟糕的CPU周期。大约在5-10%的开销范围内 因此,请以缓存有效的方式使用数据缓冲区。

2.)关键循环和MCPS密集型函数可以用汇编语言编码,也可以使用编译器为该h / w目标提供的低级内部函数。

3.)外部存储器的读/写周期很昂贵。尽可能减少外部存储器访问。或者,如果你必须以有效的方式访问它(PReloading数据,并行DMA访问等..)

一般来说,如果你通过遵循优化技术获得大约20%的优化(最好的情况),我会说这足够好并且没有任何主要的代码重构,算法重新设计。之后它变得诡计,复杂,乏味。

-AD

答案 9 :(得分:0)

我在Linux上用C和C ++进行数值计算的最优化语言。我发现分析虽然很有用,但可以扭曲你的运行时结果,以便经常调用廉价的操作(比如c ++迭代器增量)。所以带上一粒盐。 在实际策略方面,产生了良好的加速:

  1. 使用数字数组而不是对象数组。例如,c ++具有“复杂”数据类型。这些数组上的操作比两个浮点数上的类似操作要慢很多。对于任何性能关键代码,这可以概括为“使用机器类型”。
  2. 编写代码以使编译器在其优化中更有效。例如,如果您有一个固定大小的数组,请使用数组索引,以便自动矢量化(英特尔编译器的一个功能)可以工作。
  3. SIMD指令可以提供良好的加速,如果您的问题符合它们所设计的域类型(同时对浮点数或整数进行乘法/除法)。这就像MMX,SSE,SSE2等。
  4. 使用具有插值的查找表来处理昂贵的函数,其中精确值并不重要。这并不总是好的,因为在内存中查找数据本身可能很昂贵。
  5. 我希望能给你一些启发!

答案 10 :(得分:0)

正如许多其他人已经说过的那样,分析是你的第一步。

我想补充一点,关注数据结构和算法作为第一步通常比直接进入微优化更有利可图。

首先,编译器通常会为您执行许多经典优化技巧(通常比您更好)。在C#等现代语言中尤其如此,因为编译器对程序结构有更多的了解,因此在较旧的,受限制较少的语言C中尤其如此。更糟糕的是,通过“优化”来混淆代码实际上会使编译器更难以应用自己的,更有效的优化。

大多数情况下,当您开始改善运营的大部分时,还有更多的改进空间。例如,搜索链表是O(n);意味着搜索它所花费的时间以与存储在其中的数据大小相同的速率增加。搜索哈希表只是O(1),所以你可以添加更多数据而不增加搜索时间(当我们离开理论世界时还有其他因素,所以这不是真的,但是大多数情况下都是如此时间)。

与你的循环混淆,使它们从高到低运行,这样生成的代码可以用JumpIfZero而不是JumpIfLessThan节省几个时钟周期,可能不会产生相同程度的影响!

答案 11 :(得分:0)

好策略

除了上面提到的优化基本规律(测量,如果没有必要也不进行优化),虽然问题明确要求效率,但我总是重构这样的代码在我检查期间。

通常,性能不佳的代码也会被严重记录。因此,通过重构,我确保代码本身更好地记录并且更容易理解。这是确保我知道我正在优化的基础(因为在大多数情况下,这段代码的要求也不会完全可用)。

何时停止

对于性能非常糟糕的应用程序,您在探查器中显示的单个方法(或一组相关方法)的运行时通常会出现峰值,显示编程错误或设计缺陷。所以我通常会停止,如果profiled方法的运行时大部分是均匀分布的(或者如果显示的大多数瓶颈方法是平台代码,如Sun Java方法)。如果您的客户要求进一步优化,您将不得不重新设计应用程序的更大部分,而不是优化现有代码。