【发布时间】:2012-08-23 23:57:31
【问题描述】:
我试图证明 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 中的性能仍然是一个雷区。
【问题讨论】:
-
你的实现是否等同于
Arrays.sort? -
@ponzao 差不多,除了选择支点的方式。但这不可能解释这种差异。请自行联系有问题的Java code。
-
@ponzao 检查了一些;因为我用一个随机填充的数组进行基准测试,所以将第一个元素作为枢轴是非常好的。我不会在这里深入了解最坏情况数据集的本质。
-
fyi,Java SE 7 及更高版本使用 TimSort 对数组进行排序,与 Quicksort 相比,它在实际数据上的性能略好。见en.wikipedia.org/wiki/Timsort
-
不,@sw1nn 仍然是 quicksort。
标签: clojure