为什么Java在这里比C运行得更快?

灵感来自这个问题 ,

现在仅对具有> 10k rep的用户可见

我想出了以下代码:

$cat loop.c int main( int argc, char ** argv ) { int i = 0; while( i++ < 2147483647 ); } $cc -o loop loop.c $ time ./loop real 0m11.161s user 0m10.393s sys 0m0.012s $cat Loop.java class Loop { public static void main( String [] args ) { int i = 0; while( i++ < 2147483647 ); } } $javac Loop.java $time java Loop real 0m4.578s user 0m3.980s sys 0m0.048s 

为什么Java版本的运行速度比C版快3倍? 我在这里缺少什么?

这是在Ubuntu 9.04上运行的:

英特尔(R)奔腾(R)M @ 1.73GHz

32位

编辑

这真太了不起了。 在C中使用-O3选项优化循环并在Java中使用-server也是如此。 这是“优化时代”。 优化了http://img196.imageshack.us/img196/6489/screenshot4rv.png

我希望javac默认为比你的C编译器更高级别的优化。 当我在这里用-O3编译时,C更快:

C与-O3

 real 0m0.003s user 0m0.000s sys 0m0.002s 

你的java程序:

 real 0m0.294s user 0m0.269s sys 0m0.051s 

更多细节; 没有优化,C编译为:

 0000000100000f18 pushq %rbp 0000000100000f19 movq %rsp,%rbp 0000000100000f1c movl %edi,0xec(%rbp) 0000000100000f1f movq %rsi,0xe0(%rbp) 0000000100000f23 movl $0x00000000,0xfc(%rbp) 0000000100000f2a incl 0xfc(%rbp) 0000000100000f2d movl $0x80000000,%eax 0000000100000f32 cmpl %eax,0xfc(%rbp) 0000000100000f35 jne 0x00000f2a 0000000100000f37 movl $0x00000000,%eax 0000000100000f3c leave 0000000100000f3d ret 

通过优化( -O3 ),它看起来像这样:

 0000000100000f30 pushq %rbp 0000000100000f31 movq %rsp,%rbp 0000000100000f34 xorl %eax,%eax 0000000100000f36 leave 0000000100000f37 ret 

如您所见,整个循环已被删除。 javap -c Loop为java字节码提供了这个输出:

 public static void main(java.lang.String[]); Code: 0: iconst_0 1: istore_1 2: iload_1 3: iinc 1, 1 6: ldc #2; //int 2147483647 8: if_icmpge 14 11: goto 2 14: return } 

看来循环是编译进来的,我想在运行时会发生一些事情来加速这个循环。 (正如其他人提到的那样,JIT编译器会压缩循环。)

我的猜测是JIT正在优化空循环。

更新:Java性能调优文章Followup to Empty Loop Benchmark似乎支持这一点,以及其他答案,指出C代码也需要进行优化,以便进行有意义的比较。 关键报价:

如果我选择使用客户端模式1.4.1 JVM(客户端是默认模式),则不会优化循环。 如果我选择使用Microsoft的C ++编译器,C版本将花费时间。 显然,编译器的选择至关重要。

这里有一些你需要控制的东西:

  • 与启动已编译的C程序相比,JVM的启动是非常重要的
  • 你的循环没有做任何事情,编译器可能知道这一点
  • JIT编译器通常比非优化的C编译器生成更好的代码

“我在这里失踪了什么?” 优化标志。

我不认为这个问题确实有答案; 它取决于编译器执行的优化。 在这种情况下,我希望,如果进行足够的优化工作,将完全消除循环,因为i从未使用过。

优化 – 您至少在gcc命令行中缺少-O2标志。

Java JIT编译器非常智能,可以优化循环,而C编译器似乎关闭了大部分优化。

因此,您真正将启动Java机器的时间与未经优化的C代码计算到20亿的时间进行比较。

因为程序没有做任何事情,优化器可以删除循环


如果你试图让编译器做一个特定的单元,或者工作的基准 ,那么你需要愚弄它,以为实际上会使用工作的结果。

一种方法是在一个文件中编写一个函数,编译它,然后使用另一个文件中的安装程序调用它。 没有编译器可以预测将来会编译什么。

没有它,它只是默认优化级别之间的竞争,没有任何实际意义。

你的程序绝对没有任何意义,所以这对两种语言的性能都没有任何说明。 它告诉你的唯一事情是你的编译器是否能够解决这个问题,因此完全跳过你的程序。

要使它做“某事”,你必须将每个增量打印到stdout。 如果只打印最终结果,那么一个好的编译器可以将程序优化为只打印此结果并跳过整个“计算”的语句。