编写代码以帮助编译器进行优化

时间:2011-03-29 02:57:54

标签: c++ compiler-optimization

是否有人知道编译器是否有优化源代码的列表?我更喜欢GCC作为例子。

我想知道程序员应该如何处理代码以获得良好的优化并帮助编译器优化它。程序员的一些优化可能会避免编译器做更好的优化。

示例:

replace
for (int i = 0; i < n - 1; i++ )
by
int n2 = n - 1;
for (int i = 0; i < n2; i++ )


for (int i = 0; i < n/2; i++ )
by
int n2 = n/2;
for (int i = 0; i < n2; i++ )



for (int i = 0; i < obj.calc_value(); i++ ) //calc_value() will return the same result with obj remaining unchanged.
by
int y = obj.calc_value()
for (int i = 0; i < y; i++ )

保持代码易于阅读和理解非常重要。

由于

编辑:

其他例子:

  • 内联函数
  • 删除递归

4 个答案:

答案 0 :(得分:13)

说真的,把它留给编译器。我已经看到了gcc在其“疯狂”-O3级别输出的代码,并证明编写这些优化引擎的人要么是外星人,要么来自相当遥远的未来时间。

我还没有看到registerinline在我的代码性能方面有明显差异的情况。这并不意味着不会,只是说编译器编写者在从处理器中提取最后一盎司的性能时,比我们凡人更了解技巧。

就优化而言,它应该只在存在真正问题的地方进行。这意味着分析代码并发现瓶颈,但更重要的是,不优化在上下文中视为缓慢的操作。无论一次性操作是十分之一秒还是百分之一,用户都没有差别。

有时候,可读性的优化是你能做到的最好的: - )


顺便说一句,这只是一个 gcc为你做的漂亮技巧。考虑下面的代码,它应该计算阶乘并返回它:

static int fact (unsigned int n) {
    if (n == 0) return 1;
    return n * fact (n-1);
}
int main (void) {
    return fact (6);
}

这编译为(-O3):

main: pushl    %ebp            ; stack frame setup.
      movl     $720, %eax      ; just load 720 (6!) into eax.
      movl     %esp, %ebp      ; stack frame
      popl     %ebp            ;   tear-down.
      ret                      ; and return.

没错,gcc只是在编译时完成所有工作并将整个事情转化为相当于:

int main (void) { return 720; }

将此与-O0(天真)版本对比:

main:  pushl   %ebp               ; stack 
       movl    %esp, %ebp         ;   frame
       andl    $-16, %esp         ;   set
       subl    $16, %esp          ;   up.
       movl    $6, (%esp)         ; pass 6 as parameter.
       call    fact               ; call factorial function.
       leave                      ; stack frame tear down.
       ret                        ; and exit.

fact:  pushl   %ebp               ; stack
       movl    %esp, %ebp         ;   frame
       subl    $24, %esp          ;   set up.
       cmpl    $0, 8(%ebp)        ; passed param zero?
       jne     .L2                ; no, keep going.
       movl    $1, %eax           ; yes, set return to 1.
       jmp     .L3                ; goto return bit.

.L2:   movl    8(%ebp), %eax      ; get parameter.
       subl    $1, %eax           ; decrement.
       movl    %eax, (%esp)       ; pass that value to next level down.
       call    fact               ; call factorial function.
       imull   8(%ebp), %eax      ; multiply return value by passed param.

.L3:   leave                      ; stack frame tear down.
       ret                        ; and exit.

答案 1 :(得分:3)

所有建议的改进都是Loop-invariant code motion的示例,这是几乎每个优化编译器所做的基本优化。

真实编译器执行的优化范围比这些示例更先进。上面链接的维基百科文章有一些进一步阅读的链接。

答案 2 :(得分:0)

至于您发布的代码,我同意paxdiablo的回答。在很多情况下,编译器可以很好地优化而无需提示。

模板元编程

如果您希望帮助编译器进行优化(并且有理由 - 配置文件,个人资料,个人资料!),我见过的最有用的技巧是template metaprogramming

Boost has some direct support for template metaprogramming

一个有用的例子是模板元编程矩阵数学库,它可以减少完成的操作次数,同时将这些操作留在源代码中。他们还在编译时完全评估了一些操作。

以下是第一个出现在谷歌上的内容:http://arma.sourceforge.net/

Const Correctness

您应该调查的另一件事是const-correctness,因为它可以轻松帮助修改代码的错误。另一个好处是it can sometimes help with compiler optimizations

const-correctness和模板元编程都很难掌握,但非常有用。这就是C ++的大部分内容:)

答案 3 :(得分:0)

无论这里的“不优化器”告诉你,总是可以帮助(在我的情况下为C)编译器产生更好的代码。

编译器将比大多数程序员在策略性地排序代码时更好地利用可用的管道和执行单元。话虽如此,同样重要的是要指出编译器在战略规划层面上并不擅长,即高于个别程序的水平。

表现不佳的软件比表现良好的软件更常见。 “不优化者”很少承认(并且肯定不在你的帖子的答案中)的一件事是,由于(显然)编写低性能软件是可能的(显然)也可以写得很好。编译器不是炼金术:为它们提供垃圾源代码,它们将生成垃圾机代码。给他们提供合格的源代码,他们肯定会产生合格的机器代码。

我对优化问题很矛盾。一方面,如果确定了性能问题(通常在开发接近完成时),通常为时已晚,无法获得任何显着的改进(比如100%或更高),因为这需要在战略层面上工作(没有的时间)。另一方面,如果早些时候完成,那么“完整的性能图”就不可用,所以“不优化者”会说优化太早,因为没有有效的数据。优化通常是在恐慌中完成的事情,以挽救一些构造不良的软件,或者通过验收测试作为生命支持。在积极的背景下优化(使代码更有效地执行)或多或少闻所未闻(至少在这里?)。

我喜欢编写表现良好的代码的挑战。我的代码是可读的,但从第一手经验我选择某些代码构造,因为我知道它们会表现更好。此外,我从开发的一开始就对我的应用程序进行测量,以便他们自己测量,然后随着开发的进展,我可以关注性能。