情况就是这样:
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
我现在确信这与优化有关,所以现在问题更多的是关于JIT为加速计算做了什么呢?
答案 0 :(得分:31)
问题很可能是您没有在启用优化的情况下编译C版本。如果启用积极优化,gcc
生成的二进制文件应该会获胜。 JVM的JIT很好,但简单的事实是JVM必须加载然后在运行时应用JIT; gcc
可以在编译时优化二进制文件。
离开所有gcc
标志会给我一个表现相当慢的二进制文件,就像你的一样。使用-O2
为我提供了一个二进制文件,几乎失去了Java版本。使用-O3
给我一个轻松击败Java版本的版本。 (这是在我的Linux Mint 16 64位机器上,gcc
4.8.1和Java 1.8.0_20 [例如,Java 8 Update 20]。)larsmans检查了{{1}的反汇编}版本并向我保证编译器没有预先计算结果(我的C和程序集fu现在非常弱;很多感谢larsmans进行双重检查)。有趣的是,感谢Mat的调查,看起来这实际上是我使用-O3
4.8.1的副产品;早期版本和更高版本的gcc
似乎愿意预先计算结果。但是,对我们来说是一个意外的事故。
这是我的纯C版本[我还更新了它,以便考虑Ajay's comment关于你在Java版本中使用常量,但是C版本中的变量gcc
(没有制作)任何真正的差异,但......)]:
N
:
sum.c
我的Java版本与你的版本没有变化,除了我很容易忘记零:
#include <stdio.h>
int main(){
long sum=0;
long i;
for( i=0; i<1000000000; i++ ) sum+= i;
printf("%ld\n",sum);
}
:
sum.java
结果:
C二进制运行(通过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);
}
}
编译):
$ 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运行(编译时没有特殊标志,运行gcc sum.c
,微小改进):
$ time java -server -noverify sum 499999999500000000 real 0m0.651s user 0m0.649s sys 0m0.016s
C二进制运行(通过-server -noverify
编译):
$ time ./a.out 499999999500000000 real 0m0.733s user 0m0.732s sys 0m0.000s
C二进制运行(通过gcc -O2 sum.c
编译):
$ time ./a.out 499999999500000000 real 0m0.373s user 0m0.372s sys 0m0.000s
以下是我gcc -O3 sum.c
版本main
的{{1}}结果:
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
正如我所说,我的assembly-fu非常弱,但我在那里看到一个循环,而不是编译器已完成数学运算。
为了完整起见,objdump -d a.out
的结果的-O3
部分:
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正在做什么。
答案 1 :(得分:3)
与java
优化字节代码进行比较时,优化gcc
字节代码是有意义的。接受的答案不包括-server -noverify
个开关。可选地,可以应用-Xcomp
,但不建议这样做。
CPU:Intel(R)Core(TM)i7-4770 CPU @ 3.40GHz
java版“1.8.0_25” Java(TM)SE运行时环境(版本1.8.0_25-b17) Java HotSpot(TM)64位服务器VM(内置25.25-b02,混合模式)
gcc(Ubuntu 4.8.2-19ubuntu1)4.8.2
sum.java with flags:
time java -server -noverify sum
499999999500000000
real 0m0.299s
user 0m0.303s
sys 0m0.004s
和sum.c with 03 flag:
time ./a.out
499999999500000000
real 0m0.233s
user 0m0.233s
sys 0m0.000s