java为什么Clojure中嵌套循环/重复速度慢?
Clojure中的单循环/重复执行速度与Java for循环的执行速度一样快
Clojure版本:
(defn singel-loop [i-count]
(loop [i 0]
(if (= i i-count)
i
(recur (inc i)))))
(time (loop-test 100101))
"Elapsed time: 0.8857 msecs"
Java版本:
long s = System.currentTimeMillis();
for (i = 0; i < 100000; i++) {
}
System.out.println("Time: " + (System.currentTimeMillis() - s));
时间:~1ms
但是,如果您添加一个内部loop/recur
,性能绝对会下降
Clojure:
(defn double-loop [i-count j-count]
(loop [i 0]
(loop [j 0]
(if (= j j-count)
j
(recur (inc j))))
(if (= i i-count)
i
(recur (inc i)))))
(time (double-loop 100000 100000))
"Elapsed time: 70673.9189 msecs"
Java版本:
long s = System.currentTimeMillis();
for (int i = 0; i < 100000; i++) {
for (int j = 0; j < 100000; j++) {
}
}
System.out.println((System.currentTimeMillis() - s));
时间:~3ms
为什么Clojure版本的性能达到了可笑的程度,而Java版本保持不变
# 1 楼答案
你让它做了10万倍的工作,现在需要10万倍的时间。这并不奇怪,我也不会称之为“从悬崖上摔下来”。您可能会问,为什么Java版本只需要3倍的时间就可以完成100000倍的工作,但在这一点上,循环/重现通常如何执行并不是一个真正的问题。相反,问题更多的是JIT能在Java代码中创造出什么奇迹
# 2 楼答案
我认为这主要是因为Java代码对优化更加开放
根据here:
尽管如此,我无法证实这样的说法。这里的代码也不涉及无限循环,但是不管退出条件如何,空循环都同样无用。如果有什么区别的话,那么有限循环似乎是一个更合理的优化目标,因为至少无限循环有一个潜在的目的(无限期地阻塞)
一个更好的比较是尝试消除任何此类优化。我选择使用
System.out.flush
,因为println
可能非常昂贵且不一致,而且我认为任何直接影响System.out.
的东西都不会被优化掉结果如下:
1194.718969毫秒对1097毫秒
因此,Clojure可能无法编译成易于优化的代码
注意事项:
我在Tutorials Point上做了这些测试,而不是在真实的环境中。自从上次更新以来,IntelliJ对我来说已经完全不可用了,老实说,我不想为Clojure设置一个项目,也不想为Java摆弄
javac
为什么会有这些确切的数字?因为我在一个糟糕的环境中运行,我不希望网站限制我或做任何类似的事情。不管出于什么原因,在Clojure测试中,10000x1000被无限期地挂起(或者至少让我失去了耐心)。我必须把它降到10000x1000,这样它才能完成
正如我在对这个问题的评论中所指出的那样,这仍然是一种很糟糕的方法来对运行在JVM上的语言进行基准测试,正如本例所示。原因见here。我用Criterium表示Clojure。太棒了。它在测试之前为您运行代码来预热一切,并尝试处理垃圾收集之类的事情
# 3 楼答案
正如前面提到的答案,如果源代码中显示的Java嵌套循环版本需要比非嵌套循环多10000倍的时间,只需要3倍的时间(Java嵌套循环约3毫秒,而非嵌套循环约1毫秒),那么它应该会给您带来危险。我不知道为什么会发生这种情况,但有几种可能性:
(a)对于较短的版本,JVM JIT编译尚未开始,因此与嵌套循环版本相比,所有或大部分时间都花在解释字节码或执行JIT机器代码的优化程度较低的版本上
(b)JVM JIT以某种方式确定不需要运行循环,因为没有返回值,因此无论循环是否运行,都会产生相同的效果。一般来说,我建议在每个内部循环中至少进行一点计算(例如,添加两个数字,例如添加到一个运行总数中),并具有一个取决于此计算发生的返回值
我在这里创建了运行时间类似的Clojure和Java版本,您可以查看,并记录了我使用Criterium库获得的测量结果,该库多次运行相同的代码,让它先“预热”JIT,然后再对其进行多次测量,仅根据热身后执行情况报告结果
Java代码:https://github.com/jafingerhut/leeuwenhoek/blob/master/src/leeuwenhoek/java/JavaLoops.java
Clojure代码:https://github.com/jafingerhut/leeuwenhoek/blob/master/src/leeuwenhoek/clojure_loops.clj
两者的测量代码,结果在注释中:https://github.com/jafingerhut/leeuwenhoek/blob/master/src/leeuwenhoek/measure_loops.clj