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
(双字面值)无法获得的性能优势。
如果将 ma
和 mb
作为 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 毫秒范围内...
加速此功能的最佳简单方法是什么?根据 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
(双字面值)无法获得的性能优势。
如果将 ma
和 mb
作为 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 毫秒范围内...