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