cat sum100000000.cpp && cat sum100000000.java #include <cstdio> using namespace std; int main(){ long N=1000000000,sum=0; for( long i=0; i<N; i++ ) sum+= i; printf("%ld\n",sum); } public class sum100000000 { public static void main(String[] args) { long sum=0; for(long i = 0; i < 1000000000; i++) sum += i; System.out.println(sum); } }
这是结果:
time ./a.out && time java sum100000000 499999999500000000 real 0m2.675s user 0m2.673s sys 0m0.002s Picked up JAVA_TOOL_OPTIONS: -Dfile.encoding=UTF-8 499999999500000000 real 0m0.439s user 0m0.470s sys 0m0.027s
在拆卸的二进制文件中看不到任何异常的东西.但似乎c二进制显着慢.我不明白
我的猜测是工具链可能有一些问题
clang -v Apple LLVM version 5.1 (clang-503.0.40) (based on LLVM 3.4svn) Target: x86_64-apple-darwin13.4.0 Thread model: posix uname -a Darwin MacBook-Pro.local 13.4.0 Darwin Kernel Version 13.4.0: Sun Aug 17 19:50:11 PDT 2014; root:xnu-2422.115.4~1/RELEASE_X86_64 x86_64
添加:c / cpp编译没有任何特殊的二进制.这并不会改变结果.
gcc sum1b.cpp clang sum1b.cpp
添加:对于那些关心llvm,真的没有改变任何东西
$gcc sum100000000.cpp && time ./a.out gcc sum100000000.cpp && time ./a.out 499999999500000000 real 0m2.722s user 0m2.717s sys 0m0.003s
修改:O2更快:但是看起来像一个骗子
$otool -tV a.out otool -tV a.out a.out: (__TEXT,__text) section _main: 0000000100000f50 pushq %rbp 0000000100000f51 movq %rsp,%rbp 0000000100000f54 leaq 0x37(%rip),%rdi ## literal pool for: "%ld " 0000000100000f5b movabsq $0x6f05b59b5e49b00,%rsi 0000000100000f65 xorl %eax,%eax 0000000100000f67 callq 0x100000f70 ## symbol stub for: _printf 0000000100000f6c xorl %eax,%eax 0000000100000f6e popq %rbp 0000000100000f6f ret
我现在相信这与Optimization有关,所以现在更多关于JIT做什么来加快这个计算的问题?
解决方法
离开所有gcc标志给我一个二进制,执行得相当慢,就像你的.使用-O2给了我一个几乎不会输出到Java版本的二进制文件.使用-O3给我一个可以轻松打败Java版本的. (这是在我的Linux Mint 16 64位机器上,具有gcc 4.8.1和Java 1.8.0_20 [例如Java 8 Update 20]).larsmans检查了-O3版本的反汇编,并确保编译器不是预先计算结果(我的C和汇编fu在这些天非常弱;非常感谢larsmans双重检查).有趣的是,尽管如此,由于Mat的调查,这实际上是我使用gcc 4.8.1的副产品;较早和更高版本的gcc似乎愿意预先计算结果.对我们而言,快乐的事故
这是我的纯C版本[我还更新了它,以说明你在Java版本中使用常量的Ajay’s comment,但C版本中的变量N(没有任何真正的区别,但…)]:
sum.c:
#include <stdio.h> int main(){ long sum=0; long i; for( i=0; i<1000000000; i++ ) sum+= i; printf("%ld\n",sum); }
我的Java版本与你不同,我不太容易失去零的轨迹:
sum.java:
public class sum { public static void main(String[] args) { long sum=0; for(long i = 0; i < 1000000000; i++) sum += i; System.out.println(sum); } }
结果:
C二进制运行(通过gcc sum.c编译):
$time ./a.out 499999999500000000 real 0m2.436s user 0m2.429s sys 0m0.004s
Java运行(没有特殊的标志编译,运行没有特殊的运行时标志):
$time java sum 499999999500000000 real 0m0.691s user 0m0.684s sys 0m0.020s
Java运行(编译没有特殊的标志,运行-server -noverify,微小的改进):
$time java -server -noverify sum 499999999500000000 real 0m0.651s user 0m0.649s sys 0m0.016s
C二进制运行(通过gcc -O2 sum.c编译):
$time ./a.out 499999999500000000 real 0m0.733s user 0m0.732s sys 0m0.000s
C二进制运行(通过gcc -O3 sum.c编译):
$time ./a.out 499999999500000000 real 0m0.373s user 0m0.372s sys 0m0.000s
这是我的-O3版本的objdump -d a.out的主要结果:
0000000000400470 : 400470: 66 0f 6f 1d 08 02 00 movdqa 0x208(%rip),%xmm3 # 400680 400477: 00 400478: 31 c0 xor %eax,%eax 40047a: 66 0f ef c9 pxor %xmm1,%xmm1 40047e: 66 0f 6f 05 ea 01 00 movdqa 0x1ea(%rip),%xmm0 # 400670 400485: 00 400486: eb 0c jmp 400494 400488: 0f 1f 84 00 00 00 00 nopl 0x0(%rax,%rax,1) 40048f: 00 400490: 66 0f 6f c2 movdqa %xmm2,%xmm0 400494: 66 0f 6f d0 movdqa %xmm0,%xmm2 400498: 83 c0 01 add $0x1,%eax 40049b: 66 0f d4 c8 paddq %xmm0,%xmm1 40049f: 3d 00 65 cd 1d cmp $0x1dcd6500,%eax 4004a4: 66 0f d4 d3 paddq %xmm3,%xmm2 4004a8: 75 e6 jne 400490 4004aa: 66 0f 6f e1 movdqa %xmm1,%xmm4 4004ae: be 64 06 40 00 mov $0x400664,%esi 4004b3: bf 01 00 00 00 mov $0x1,%edi 4004b8: 31 c0 xor %eax,%eax 4004ba: 66 0f 73 dc 08 psrldq $0x8,%xmm4 4004bf: 66 0f d4 cc paddq %xmm4,%xmm1 4004c3: 66 0f 7f 4c 24 e8 movdqa %xmm1,-0x18(%rsp) 4004c9: 48 8b 54 24 e8 mov -0x18(%rsp),%rdx 4004ce: e9 8d ff ff ff jmpq 400460
正如我所说,我的汇编很弱,但我看到一个循环,而不是编译器完成了数学.
而只是为了完整,javap -c的结果的主要部分总和:
public static void main(java.lang.String[]); Code: 0: lconst_0 1: lstore_1 2: lconst_0 3: lstore_3 4: lload_3 5: ldc2_w #2 // long 1000000000l 8: lcmp 9: ifge 23 12: lload_1 13: lload_3 14: ladd 15: lstore_1 16: lload_3 17: lconst_1 18: ladd 19: lstore_3 20: goto 4 23: getstatic #4 // Field java/lang/System.out:Ljava/io/PrintStream; 26: lload_1 27: invokevirtual #5 // Method java/io/PrintStream.println:(J)V 30: return
它不是在字节码级别预先计算结果;我不能说JIT正在做什么.