Here它清楚地解释了如何优化处理原始值的Clojure程序:使用类型注释和未经检查的数学,它将快速运行:
(set! *unchecked-math* true)
(defn add-up ^long [^long n]
(loop [n n i 0 sum 0]
(if (< n i)
sum
(recur n (inc i) (+ i sum)))))
所以,出于好奇,我已经在lein repl
中尝试了它,令我惊讶的是,发现此代码的运行速度比预期的慢20倍(Oracle JDK 1.8.0_11上的Clojure 1.6.0) 64):
user=> (time (add-up 1e8))
"Elapsed time: 2719.188432 msecs"
5000000050000000
Scala 2.10.4中的等效代码(相同的JVM)在~90ms内运行:
def addup(n: Long) = {
@annotation.tailrec def sum(s: Long, i: Long): Long =
if (i == 0) s else sum(s + i, i - 1)
sum(0, n)
}
那么,我在Clojure代码示例中缺少什么?为什么它这么慢(理论上应该大致相同的速度)?
答案 0 :(得分:18)
使用lein repl
进行基准测试通常是一个坏主意,因为它专门设置了非服务器JVM设置。直接使用Clojure JAR,在OS X 10.9下运行JDK 8的3.5ghz i7 iMac上看到~40ms。
答案 1 :(得分:5)
继@dnolen's answer之后,还有一些观察结果:
虽然事实证明没有真正的区别,但我们应该让Clojure函数与Scala函数的形状相同。在
(defn add-up ^long [^long n]
(loop [n n i 0 sum 0]
(if (< n i)
sum
(recur n (inc i) (+ i sum)))))
n
未被recur
更改,因此无需受到约束
loop
。修补这些不一致之处,我们得到了
defn add-up [^long n]
(loop [sum 0, i n]
(if (zero? i)
sum
(recur (+ sum i) (dec i)))))
(Scala类型系统确保参数n
在通话时转换为Long
。据我了解(如果我错了请纠正我),Clojure {{1} }类型提示承诺很好地处理^long
参数,但不承诺将Long
转换为Double
到1e8
。但是当我制作时,我的结果非常不一致相应的变化。)
在我的笔记本电脑上,上面给出了
Long
如果删除类型提示
(time (add-up 100000000))
"Elapsed time: 103.636782 msecs"
5000000050000000
...经过的时间乘以大约二十:
(defn add-up [n]
...
)
所有这些都在OpenJDK Java 7上的Clojure 1.5.0上。
答案 2 :(得分:0)
在一本有三年历史的Mac书上,我得到以下内容:
(time (add-up 1e8))
"Elapsed time: 68.536 msecs"
和
(time (add-up 1e9))
"Elapsed time: 771.157 msecs"
=> 500000000500000000
您正在使用的Lein版本是什么,您可以通过以下方式查看clojure版本:
(clojure-version)