Clojure 性能优化与同等优化 Java

Clojure Performance optimzation vs Equivalent Java

加速此功能的最佳简单方法是什么?根据 Criterium,java 中的等效代码快了近 50 倍。

我敢打赌,如果我使用 java 数组并减少装箱量会有帮助,但我想我会先 post 看看是否有任何基本错误我正在使它可以很容易地修复。请注意,我已经为 Clojure 指出了 (double...),这大大提高了性能,但仍然不如 Java。我还首先使用 (double-array...) 而不是在函数内部使用 (vec ...) 转换了 seq,这也提高了性能,但同样,没有像 Java.

(defn cosine-similarity [ma mb]
  (let [va (vec ma), vb (vec mb)]
    (loop [p (double 0)
           na (double 0)
           nb (double 0)
           i (dec (count va))]
      (if (neg? i)
        (/ p (* (Math/sqrt na) (Math/sqrt nb)))
        (let [a (double (va i))
              b (double (vb i))]
          (recur (+ p (* a b))
                 (+ na (* a a))
                 (+ nb (* b b))
                 (dec i)))))))

请注意,ma 和 mb 都是 seq,每个包含 200 个 Double。在 java 版本中,它们作为 double[] args 传递。

使用 (double 0) 没有直接指定 0.0(双字面值)无法获得的性能优势。

如果将 mamb 作为 double-array 传递并提示参数为 doubles,您将获得更好的性能,不要将它们转换为向量通过 vec,并使用 aget 进行元素查找。这应该让您得到非常接近 java 代码性能的东西。

如果您使用双数组作为函数参数,则 let 块内的 double 调用将不需要。

最终结果应如下所示:

(defn cosine-similarity [^doubles ma ^doubles mb]
  (loop [p 0.0
         na 0.0
         nb 0.0
         i (dec (count va))]
    (if (neg? i)
      (/ p (* (Math/sqrt na) (Math/sqrt nb)))
      (let [a (aget va i)
            b (aget vb i)]
        (recur (+ p (* a b))
               (+ na (* a a))
               (+ nb (* b b))
               (dec i))))))

您尝试添加了吗?

(set! *unchecked-math* true)

既然你知道范围,你可能会用它来获得额外的速度。

编辑:@noisesmith 是对的,双数组 键入输入会产生巨大的差异。

编辑 2:在 Alex Miller 发表评论后获得极快的结果。

(set! *unchecked-math* true)

(defn ^double cosine-similarity
  [^doubles va ^doubles vb] 
    (loop [p 0.0
    na 0.0
    nb 0.0
    i  (dec (alength va))]
  (if (< i 0)
    (/ p (* (Math/sqrt na) (Math/sqrt nb)))
    (let [a  (aget va i)
          b  (aget vb i)]
       (recur (+ p (* a b))
         (+ na (* a a))
         (+ nb (* b b))
         (dec i))))))

 (defn rand-double-arr [n m]
   (double-array
     (take n (repeatedly #(rand m)))))

 (def ma (rand-double-arr 200 10000))
 (def mb (rand-double-arr 200 10000))

 ; using do times
 (dotimes [_ 30] (time (cosine-similarity ma mb)))
 ; ...
 ; "Elapsed time: 0.003537 msecs"

 ; using criterium: [criterium "0.4.3"]
 (use 'criterium.core)
 (quick-bench (cosine-similarity ma mb))
 ; 
 ; Execution time mean           : 2.072280 µs
 ; Execution time std-deviation  : 214.653997 ns
 ; Execution time lower quantile : 1.765412 µs ( 2.5%)
 ; Execution time upper quantile : 2.284536 µs (97.5%)
                   Overhead used : 6.128119 ns

第一个版本在 500~1000 毫秒范围内...