我试图证明 Clojure 的性能可以与 Java 平起平坐。我发现的一个重要用例是快速排序。我写了一个实现如下:
(set! *unchecked-math* true)
(defn qsort [^longs a]
(let [qs (fn qs [^long low, ^long high]
(when (< low high)
(let [pivot (aget a low)
[i j]
(loop [i low, j high]
(let [i (loop [i i] (if (< (aget a i) pivot)
(recur (inc i)) i))
j (loop [j j] (if (> (aget a j) pivot)
(recur (dec j)) j))
[i j] (if (<= i j)
(let [tmp (aget a i)]
(aset a i (aget a j)) (aset a j tmp)
[(inc i) (dec j)])
[i j])]
(if (< i j) (recur i j) [i j])))]
(when (< low j) (qs low j))
(when (< i high) (qs i high)))))]
(qs 0 (dec (alength a))))
a)
此外,这有助于调用 Java 快速排序:
(defn jqsort [^longs a] (java.util.Arrays/sort a) a))
现在,为了基准。
user> (def xs (let [rnd (java.util.Random.)]
(long-array (repeatedly 100000 #(.nextLong rnd)))))
#'user/xs
user> (def ys (long-array xs))
#'user/ys
user> (time (qsort ys))
"Elapsed time: 163.33 msecs"
#<long[] [J@3ae34094>
user> (def ys (long-array xs))
user> (time (jqsort ys))
"Elapsed time: 13.895 msecs"
#<long[] [J@1b2b2f7f>
性能是天壤之别(一个数量级,然后是一些)。
有什么我遗漏的,我可能使用过的任何 Clojure 功能吗?我认为性能下降的主要来源是当我需要从循环中返回多个值并且必须为此分配一个向量时。这可以避免吗?
顺便说一句,运行 Clojure 1.4。另请注意,我已多次运行基准测试以预热 HotSpot。这些是他们安定下来的时候。
更新
我的代码中最可怕的弱点不仅仅是向量的分配,而是它们强制装箱并破坏原始链的事实。另一个弱点是使用结果,loop
因为它们也会破坏链条。是的,Clojure 中的性能仍然是一个雷区。