- 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正在做什么.