简短回答:
如果按照题主给出的Java和Python代码,在常见环境里按照默认配置来跑测试,确实会发现Python版例子比Java版例子跑得快。这自然也算是Java性能的一个坑啦——从来就没有什么“理论上Java应该比Python快”的理论,而是在知道常见性能坑、知道best practice的前提下,纯Java程序在较大规模的运算上有可能比纯Python程序有更好的性能,并且相对来说更scalable。
话说回来,这个例子要让Java比Python跑得快无需修改代码,只要稍微修改一下Java的启动参数即可。另外我也相信这个小例子不一定充分反映了题主说的“需要3个小时以上”的那个Java程序的性能问题。那个程序或许也只需要稍微调整一下启动参数就可以大幅提高性能,也可能要修改代码去掉一些不好的做法。
所以具体到题主这个例子,是什么造成Java比Python慢得多?是HashMap写得没dict好?是因为HashMap用Java实现而dict用C实现?是因为JIT的预热开销?还是别的?
假定题主运行Java和Python的环境都是常见的,例如说Oracle JDK vs 原装CPython,那么在Java一侧讨论的对象就是Oracle JDK里的HotSpot VM的性能。
答案是:是因为题主没有设置好GC的参数,而HotSpot VM默认的GC参数在这个例子上非常不适用,导致默认参数下Java版的性能非常糟糕。Java的HashMap自身并不慢,特别是经过JIT编译后它其实不会比CPython的dict慢,至少不是造成这个例子的性能差异的原因。
在我的Mac OS X 10.9 / x86-64上,用Oracle JDK8u101来跑题主给出的Java版测试,我可以看到以下的用时情况:
# 默认参数
$ java TestHashMap
...
Round 0: Time duration: 8480ms
...
Round 1: Time duration: 4849ms
...
Round 2: Time duration: 3789ms
# 添加GC参数,设定Java堆的大小
$ java -Xmx3200m -Xms3200m -Xmn3g TestHashMap
...
Round 0: Time duration: 1801ms
...
Round 1: Time duration: 1810ms
...
Round 2: Time duration: 1122ms
而同一环境中我用CPython 2.7.5(是的这个有点老…)来运行题主的Python脚本的话,用时情况是:
$ time python test_dict.py
...
real0m9.082s
user0m8.192s
sys0m0.825s
(是的这个版本有点老;是的我偷懒了,这样计时跟我这边用的Java版的计时方式不一样所以并不公平,这边会包括了CPython自身的解释器初始化开销,但CPython解释器的初始化开销非常低,至少比HotSpot VM的VM初始化要低多了。这样计时不会引起量级上的差异。)
我用的测试代码是直接将题主的Python代码放进test_dict.py里,Java版则是直接将题主给的代码包装进一个方法里然后从main()中循环调用3次:
import java.util.*;
public class TestHashMap {
public static void doTest() {
Map testmap = new HashMap();
for (int i &#61; 0; i <10000000; i&#43;&#43;) {
String s &#61; Integer.toString(i);
testmap.put(s, i);
int t &#61; testmap.get(s);
if (t % 100000 &#61;&#61; 0) {
System.out.println(t);
}
}
}
public static void main(String[] args) {
for (int i &#61; 0; i <3; i&#43;&#43;) {
long start &#61; System.currentTimeMillis();
doTest();
long end &#61; System.currentTimeMillis();
System.out.printf("Round %d: Time duration: %dms\n", i, (end - start));
// System.gc(); }
}
}
这样的Java版测试程序&#xff0c;在Oracle JDK8u101上以默认参数运行时&#xff0c;3次计时的差异的主要来源其实不是JIT编译的影响&#xff0c;而是我的环境中HotSpot VM默认选择的ParallelGC的自适应逻辑的初始化——它需要通过头几次GC来逐渐“学习”当前Java应用的行为并调整自己的GC参数&#xff0c;所以头几次GC本身就是会比较慢。外加默认的GC堆大小对这个程序来说太小了&#xff0c;导致会多次触发GC(包括多次Full GC)&#xff0c;这就会严重降低Java程序的性能。
只要正确设置GC参数就可以完全避开这些问题。如何给Java程序正确设置GC参数也算是Java进阶学习的一门必修课了(残念