代码之家  ›  专栏  ›  技术社区  ›  Stevo

常见的预成熟优化和微观优化

  •  7
  • Stevo  · 技术社区  · 7 年前

    我做了很多相对简单的数值编程,以及大量的编程。在那个时候,我发现很多人倾向于通过怀疑来执行我所说的优化,或者通过道听途说来执行优化。其中一些确实是优化(实际上,其中一些在实际执行速度上有显著差异),而有些则不是。我正试图编译一个列表,最终的目标是对它们进行分析,看看它们是否真的与现代编译器有所不同,以及它们对现代编译器的贡献有多大。

    一些例子可能是:

    • x++ 对战 ++x
    • 如果你必须计算 1/2*a*b*c/(d*e)*x^2) 在一个循环中, a, b, c, d, e 不要在循环内改变,预先计算系数会更快。
    • 在Fortran中,数组的布局使最后一个索引变化最慢,因此我们以k、j、i顺序嵌套循环,而不是以i、j、k顺序嵌套循环,以提高缓存的使用率。
    • 避免内部循环中的if语句
    11 回复  |  直到 13 年前
        1
  •  15
  •   peterchen    17 年前

    许多过早优化的坏名声来自不同的时代。

    然后:

    • 内存不足,但访问成本低
    • 说明书很贵
    • 优化器很蠢,很容易手动超越。你的编译器需要很多“提示”
    • (至少对于x86平台而言):乘法很慢,除法是三倍的,所以浮点运算可能需要很长时间。源到机器的代码相当简单,CPU或多或少会一步一步地执行。

    现在:

    • 对于大多数应用程序来说,内存不足,但访问是强非线性的(热点访问仍然很便宜)
    • 说明书很便宜
    • 优化器比我们大多数人都聪明。他们甚至比我们中的许多人更能理解C++标准。
    • (x86)我们已经从一个cisc架构转移到了一个执行有风险cisc代码的有风险的架构。只有Agner Fog和优化器知道这是什么意思。

    需要注意的一点是,当从桌面转移到嵌入式系统时,一般的技术倒退了十年。


    所以这里有一个列表,我要么把它留给我的编译器,要么不再麻烦了。

    • 用移位和加法替换常量乘法 (e.g. x*5 == x+(x<<2))
    • 用溢出乘法将整数除法替换为常量
    • 预计算常数
    • 查找表,例如用于快速但不精确的正弦计算。
    • 整数交换的xor fpor
    • 从循环中提升简单常量计算
    • 展开短等长循环

    过早的优化也不错。否则,我们都必须使用冒泡排序,直到证明该排序是一个瓶颈:—) Knuth在他最初的引用中,把重点放在小事情上,而不是在更高层次上处理性能——设计、数据访问模式和算法。

    早期优化(或“怀疑优化”,如你所说,我喜欢这个术语)只有在没有被证明是有效的情况下才是邪恶的。 和 降低了代码的另一个方面(如可读性、简单性或灵活性)。

    另外,库代码也有一定的回旋余地。毕竟,您不知道每个最终用户操作调用新的unbuddleString函数的频率。

        2
  •  2
  •   Malaxeur    17 年前

    我最喜欢的方法调用不是在for循环的条件下放置方法调用。

    示例(在某些带有循环的语言中):

    for (int i=0; i<a.size(); ++i) {
       //Do something
    }
    

    我记得读过一篇关于2006年的竞争对手的文章,文章的目标是写出在不同平台上以极为不同的速度运行的完美代码。胜利者基本上写了一个for循环,条件是使用strlen调用,导致使用cygwin的gcc(或类似的方法)的Windows速度下降100倍。基本上,如果每次都要进行评估,而且值不会改变…不要相信编译器会将它拉出;自己移动它。

        3
  •  1
  •   Bill the Lizard    17 年前

    我见过两个:

    • 尽可能使用最小的数据类型。(浮动代替双精度,短代替整数。)
    • 使用数组而不是列表。
        4
  •  1
  •   Carl    17 年前

    通过编译器提示或复制和粘贴代码过早地内联函数/方法。

        5
  •  1
  •   Jonathan Graehl    17 年前

    这很棘手,因为有些明显的优化是在没有程序员干预的情况下进行的,尽管情况一直在好转。

    C++中用户定义类的X++可能会较慢。对于原始人来说,不是很多。

    C++编译器 routinely fail 根据类型折叠多个常量加法、除法等。

    多维阵列扫描顺序 将 如果整个数组太大而无法容纳在一级缓存中,则很重要。

        6
  •  0
  •   David Rutten    17 年前
    • 我公司的许多程序员将很容易切换到指针数学(C++),而不是使用引用、数组索引操作符等。
    • 在小数据类型的函数参数中使用const pointers/references,复制速度可能也一样快。
        7
  •  0
  •   kriss    17 年前

    一个非常常见的:

    • 避免创建函数,因为函数调用应该非常昂贵

    一个很常见的问题(但希望不会再出现,因为它引发了许多其他问题,并且在OO编程中变得毫无意义):

    • 使用全局参数而不是向函数传递常量参数

    两个我认为与目标完全相反的任务(使编译器任务更困难,实现的优化更少):

    • 重用现有变量
    • 避免在循环块中创建局部变量

    有时我使用的一些(正如我在过去的编译器中所证明的那样,它们管理得不好,有时性能会带来巨大的好处…还是这样吗?)

    • 使用static避免将本地函数导出到模块外部
    • 通过直接包含源文件(包括“source.c”)绕过链接器。

    一些古老的经典:

    • 与0或空值的隐式比较( while (*x++) 诸如此类)
        8
  •  0
  •   bill    17 年前

    X++对X++不是一个优化问题,而是一个明确的意图问题。你真的想用x的旧值吗?(提示:通常不会)如果你这样做了,继续使用x++,但是另外,向两年后查看它的维护程序员指出,这只是一个增量,没有更多。

    正如你不会在没有常量的情况下声明某些东西,如果它在语义上永远不应该改变,那么你应该使用前/后增量来显示你的意图。

        9
  •  0
  •   James Black    17 年前

    在for循环中不声明变量。

    for (int t = 0; t < 5; t++)
      String s = "Number" + t;
    };
    

    在某些语言中,我发现这一点更好,因为它似乎可以减少一些问题:

    String s;
    for (int t = 0; t < 5; t++) {
      s = "Number" + t;
    }
    
        10
  •  0
  •   Mike Dunlavey    9 年前

    人们有一种倾向,把注意力集中在一些小事情上,对它进行评判,同时忽略了全局。设计大型软件的许多方法都隐含着性能的基本原理,因此,在我看来,这构成了过早的优化。实例:

    • 具有哈希编码的集合。散列编码可能有O(1)行为(一种性能原理),但对于合理的大小集合来说,可能会造成过度杀伤力。

    • 通知样式编程。数据很难完全非规范化,因此程序员需要有一种方法来处理非规范化结构(其状态不一致)。通常使用的方法是让getter和setter具有“属性”,鼓励它们在整个数据结构中瞬间支持更改,以保持一致。其基本原理是 更好的性能 与容忍一定数量的不一致并单独处理的策略相比。当性能调优完成后,可以看到这些维护一致性的尝试会导致大量额外的工作在响应小的更改时完成。

    • 类太多,数据结构太多。通常,拥有一个类并创建对象的基本原理是,它是程序员头脑中的一个概念,在这种情况下,通过某种O(N)扫描可以很容易地从其他数据中提取相同的信息。当人们认为 性能不佳 (恐怖)。因此,性能成为额外数据结构的一个隐含的过早理由。

    • 迭代器。鼓励人们使用带有迭代器的标准集合类,并告诉他们编译器将很好地优化迭代器(即它们将具有良好的性能)。那么,当它们被用于低级循环时,猜猜怎么着?单步执行程序集级别的代码,它们将经历所有类型的通用性,这是任何编译器都无法理解的。在没有函数调用的内部循环中,应该怀疑迭代器。

    在我在性能调优方面所做的工作中,我经常发现自己不得不撕开一个老鼠窝,因为它的过量数据结构会导致大量的性能问题。而导致数据结构过剩的最常见原因是“性能”。

    Here's a small example.

    补充:还有其他可能导致绩效问题的错误预期问题,例如过度渴望全球化。在应用程序启动时不必要地从资源中提取字符串并不便宜。

        11
  •  0
  •   nawfal    13 年前

    我发现的一些常见的(在.NET世界中有些是特定的):

    1. 使用较小的整数类型而不是 Int32 .

    2. ++i 代替使用 i++

    3. 利用 StringBuilder 类来concat少量字符串

    4. for 而不是 foreach

    5. 对类型验证和类型转换的一般厌恶,如:

      if (i am Handsome)
          myself = (Handsome)i
      
    6. O(1)数据结构开裂 不知道O(1)是什么。如果输入很小,哈希表会产生不利影响。

    7. 对…的厌恶 try-catch

    8. 使方法和变量基于性能而非意图静态化。

    9. 避免关闭变量

      string p;
      foreach (var item in collection)
          p = ...;
      
    10. 使用 char 在字符串串联期间替换字符串 ,像:

      string me = 'i' + "me myself"; // something along that line - causes boxing
      
    11. SELECT COUNT(1) FROM table 而不是 SELECT COUNT(*) FROM table

    (粗体为“将”产生不利影响,斜体为“可能”)。

    也就是说,第5点就是我看到自己在做的事情(或者说不做得很精确),我喜欢这样。 as 优化引用类型..:)

    推荐文章