昂贵算法的 Clojure 性能
我已经实现了一种算法来计算最长的连续公共子序列(不要与最长的公共子序列混淆,尽管对于这个问题并不重要)。我需要从中榨取最大的性能,因为我会经常调用它。我已经在Clojure和Java中实现了相同的算法,以便比较性能。Java 版本的运行速度明显更快。我的问题是,我是否可以对Clojure版本做些什么来将其加速到Java的水平。
下面是 Java 代码:
public static int lcs(String[] a1, String[] a2) {
    if (a1 == null || a2 == null) {
        return 0;
    }
    int matchLen = 0;
    int maxLen = 0;
    int a1Len = a1.length;
    int a2Len = a2.length;
    int[] prev = new int[a2Len + 1]; // holds data from previous iteration of inner for loop
    int[] curr = new int[a2Len + 1]; // used for the 'current' iteration of inner for loop
    for (int i = 0; i < a1Len; ++i) {
        for (int j = 0; j < a2Len; ++j) {
            if (a1[i].equals(a2[j])) {
                matchLen = prev[j] + 1; // curr and prev are padded by 1 to allow for this assignment when j=0
            }
            else {
                matchLen = 0;
            }
            curr[j+1] = matchLen;
            if (matchLen > maxLen) {
                maxLen = matchLen;
            }
        }
        int[] swap = prev;
        prev = curr;
        curr = swap;
    }
    return maxLen;
}
以下是相同的Clojure版本:
(defn lcs
  [#^"[Ljava.lang.String;" a1 #^"[Ljava.lang.String;" a2]
  (let [a1-len (alength a1)
        a2-len (alength a2)
        prev (int-array (inc a2-len))
        curr (int-array (inc a2-len))]
    (loop [i 0 max-len 0 prev prev curr curr]
      (if (< i a1-len)
        (recur (inc i)
               (loop [j 0 max-len max-len]
                 (if (< j a2-len)
                   (if (= (aget a1 i) (aget a2 j))
                     (let [match-len (inc (aget prev j))]
                       (do
                         (aset-int curr (inc j) match-len)
                         (recur (inc j) (max max-len match-len))))
                     (do
                       (aset-int curr (inc j) 0)
                       (recur (inc j) max-len)))
                   max-len))
               curr
               prev)
        max-len))))
现在,让我们在我的计算机上测试这些:
(def pool "ABC")
(defn get-random-id [n] (apply str (repeatedly n #(rand-nth pool))))
(def a1 (into-array (take 10000 (repeatedly #(get-random-id 5)))))
(def a2 (into-array (take 10000 (repeatedly #(get-random-id 5)))))
爪哇岛:
(time (Ratcliff/lcs a1 a2))
"Elapsed time: 1521.455 msecs"
Clojure:
(time (lcs a1 a2))
"Elapsed time: 19863.633 msecs"
Clojure速度很快,但仍然比Java慢一个数量级。我能做些什么来缩小这个差距吗?或者我已经把它最大化了,一个数量级是“最小的Clojure开销”。
如您所见,我已经在使用循环的“低级”构造,我正在使用本机Java数组,并且我已经对参数进行了类型提示以避免反射。
有一些算法优化是可能的,但我现在不想去那里。我很好奇我能与Java性能有多接近。如果我不能缩小差距,我就只使用Java代码。这个项目的其余部分在Clojure中,但也许有时为了性能而下降到Java是必要的。