最長連続共通サブシーケンスを計算するアルゴリズムを実装しました(最長共通サブシーケンスと混同しないでください。ただし、この質問には重要ではありません)。何度も呼び出すので、ここから最大のパフォーマンスを絞り出す必要があります。パフォーマンスを比較するために、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"
クロージャ:
(time (lcs a1 a2))
"Elapsed time: 19863.633 msecs"
Clojure は高速ですが、それでも Java よりも 1 桁遅いです。このギャップを埋めるために私にできることはありますか? または、私はそれを最大限に活用しましたが、1 桁は「最小限の Clojure オーバーヘッド」です。
ご覧のとおり、ループの「低レベル」構造を既に使用しており、ネイティブ Java 配列を使用しており、リフレクションを避けるためにパラメーターに型ヒントを付けています。
いくつかのアルゴリズムの最適化が可能ですが、今はそこに行きたくありません。Java のパフォーマンスにどれだけ近づくことができるか興味があります。ギャップを埋めることができない場合は、Java コードを使用します。このプロジェクトの残りの部分は Clojure にありますが、パフォーマンスのために Java に落とし込む必要がある場合もあります。