Performance 为什么编译器如此愚蠢?

Performance 为什么编译器如此愚蠢?,performance,language-agnostic,compiler-construction,Performance,Language Agnostic,Compiler Construction,我总是想知道为什么编译器不能找出肉眼可见的简单东西。他们做了很多简单的优化,但从来没有做过哪怕是一点点复杂的事情。例如,这段代码在我的计算机上打印零值大约需要6秒钟(使用java 1.6): intx=0; 对于(int i=0;i

我总是想知道为什么编译器不能找出肉眼可见的简单东西。他们做了很多简单的优化,但从来没有做过哪怕是一点点复杂的事情。例如,这段代码在我的计算机上打印零值大约需要6秒钟(使用java 1.6):

intx=0;
对于(int i=0;i<100*1000*1000*1000;++i){
x+=x+x+x+x+x;
}
System.out.println(x);
很明显,x从来没有改变过,所以无论你多久加一次0,它都会保持为零。因此,理论上,编译器可以用System.out.println(0)替换它

甚至更好,这需要23秒:

public int slow(){
字符串s=“x”;
对于(int i=0;i<100000;++i){
s+=“x”;
}
返回10;
}
首先,编译器可能会注意到我实际上创建了一个100000“x”的字符串s,因此它可以自动使用s StringBuilder,或者更好地直接用结果字符串替换它,因为它总是相同的。其次,它没有意识到我实际上根本没有使用字符串,因此整个循环可能会被丢弃

为什么在投入了如此多的人力投入到快速编译器中之后,它们还是那么的愚蠢


编辑:当然,这些都是愚蠢的例子,不应该在任何地方使用。但是,每当我不得不将一段漂亮且可读性很强的代码重写为不可读的代码,以便编译器感到高兴并生成快速代码时,我就想知道为什么编译器或其他自动化工具不能帮我完成这项工作。

真的吗?为什么会有人编写这样的真实代码?这里的“愚蠢”实体是代码,而不是编译器。就我个人而言,我非常高兴编译器编写人员不用费心浪费时间来优化类似的东西

编辑/澄清:
我知道问题中的代码只是一个例子,但这正好证明了我的观点:要么你必须尝试,要么你完全不知道如何编写这样效率极低的代码。编译器的工作不是握着我们的手,这样我们就不会编写可怕的代码。作为编写代码的人,我们有责任充分了解我们的工具,以便高效、清晰地编写代码。

它迫使您(程序员)思考您正在编写的内容。强制编译器为您完成工作对任何人都没有帮助:它会使编译器变得更复杂(更慢!),使您更愚蠢,对代码的关注度更低。

在您的第一个示例中,这是一种只有在值为零时才起作用的优化。编译器中需要额外的
if
语句来查找这个罕见的子句,这可能不值得(因为它必须在每个变量上检查)。此外,这又如何:

int x = 1;
int y = 1;
int z = x - y;
for (int i = 0; i < 100 * 1000 * 1000 * 1000; ++i) {
    z += z + z + z + z + z;
}
System.out.println(z);
intx=1;
int y=1;
intz=x-y;
对于(int i=0;i<100*1000*1000*1000;++i){
z+=z+z+z+z+z+z;
}
系统输出打印ln(z);
显然,这仍然是同一件事,但现在有一个额外的情况,我们必须在编译器中进行编码。有无限多的方式,它可能最终为零,而这些方式不值得编码,我想你可以说,如果你想要其中一个,你最好把它们全部都拥有

有些优化确实考虑到了您发布的第二个示例,但我认为我在函数式语言中看到的更多,而不是Java。在较新的语言中,最大的困难是。现在,
+=
可以有一个新的程序,这意味着如果我们对它进行优化,它可能是错误的(例如,向打印当前值的
+=
添加功能将意味着一个完全不同的程序)

但归根结底还是同一件事:为了确保没有可能改变最终程序状态的副作用,你必须寻找太多的案例


花一点时间,确保你所写的是你真正想让电脑做的,这样做会更容易

从C/C++的角度讲:

您的第一个示例将由大多数编译器进行优化。如果Sun的java编译器真的执行这个循环,那就是编译器错误,但是我的话是,任何POST 1990 C++、C++或FORTRAN编译器都完全消除了这样的循环。 您的第二个示例无法在大多数语言中进行优化,因为内存分配是将字符串连接在一起的副作用。如果编译器优化代码,内存分配模式就会改变,这可能导致程序员试图避免的效果。内存碎片和相关问题是嵌入式程序员每天仍然面临的问题


总的来说,我对编译器现在所能做的优化感到满意。

我认为您低估了确保一段代码不影响另一段代码所需的工作量。只需对示例x、i和s稍作修改,就可以指向相同的内存。一旦其中一个变量是指针,就很难根据指向什么来判断哪些代码可能有副作用


此外,我认为编程人员更愿意花时间进行优化,而这些优化对人类来说并不容易。

,因为我们还没有做到这一点。你可以很容易地问,“为什么我还需要写程序……为什么我不能直接输入需求文档,让计算机为我写应用程序?”

编译器编写人员将时间花在小事情上,因为应用程序程序员往往会错过这些类型的事情


而且,他们也不能假设太多(也许你的循环是某种犹太人区的时间延迟之类的)?

你编译发布代码了吗?我认为一个好的编译器会在第二个示例中检测到字符串从未被使用,从而删除整个循环。

这是一个永恒的军备竞赛赌注
x = 0
sleep 6 // Let's assume this is defined somewhere.
print x
"starting windows"
"enjoy freecell/solitaire"
"shutting down windows"
extern int x; // defined elsewhere
for (int i = 0; i < 100 * 1000 * 1000 * 1000; ++i) {
    x += x + x + x + x + x;
}
return x;
extern volatile int x; // defined elsewhere
for (int i = 0; i < 100 * 1000 * 1000 * 1000; ++i) {
    x += x + x + x + x + x;
}
return x;
[js@HOST2 java]$ gcj --main=Optimize -O2 Optimize.java
[js@HOST2 java]$ ./a.out
0
[js@HOST2 java]$
class Optimize {
    private static int doIt() {
        int x = 0;
        for (int i = 0; i < 100 * 1000 * 1000 * 1000; ++i) {
            x += x + x + x + x + x;
        }
        return x;
    }
    public static void main(String[] args) {
        for(int i=0;i<5;i++) {
            doIt();
        }
    }
}
1       java.lang.String::hashCode (60 bytes)
1%      Optimize::doIt @ 4 (30 bytes)
2       Optimize::doIt (30 bytes)
#include <stdio.h>  /* printf() */

int factorial(int n) {
   return n == 0 ? 1 : n * factorial(n - 1);
}

int main() {
   int n = 10;

   printf("factorial(%d) = %d\n", n, factorial(n));

   return 0;
}
    factorial:
   .LFB13:
           testl   %edi, %edi
           movl    $1, %eax
           je  .L3
           .p2align 4,,10
           .p2align 3
   .L4:
           imull   %edi, %eax
           subl    $1, %edi
           jne .L4
   .L3:
           rep
           ret
    main:
   .LFB14:
           subq    $8, %rsp
   .LCFI0:
           movl    $3628800, %edx
           movl    $10, %esi
           movl    $.LC0, %edi
           xorl    %eax, %eax
           call    printf
           xorl    %eax, %eax
           addq    $8, %rsp
           ret
#include <stdio.h>
int main()
{
    int x = 0;
    for (int i = 0; i < 100 * 1000 * 1000 * 1000; ++i) {
        x += x + x + x + x + x;
    }
    printf("%d", x);
}
10 FOR X = 1 TO 1000
20 NEXT : REM 1-SECOND DELAY