【问题标题】:Clojure Performance optimzation vs Equivalent JavaClojure 性能优化与等效 Java
【发布时间】:2015-01-02 00:17:20
【问题描述】:

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

我敢打赌,如果我使用 java Array 并减少装箱量会有所帮助,但我想我会先在这里发帖,看看我犯的任何基本错误是否可以轻松修复。注意我已经为 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 传递。

【问题讨论】:

    标签: java performance clojure


    【解决方案1】:

    使用(double 0) 没有直接指定0.0(双字面值)所带来的性能优势。

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

    如果您使用双数组作为函数 args,则不需要 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))))))
    

    【讨论】:

    • 谢谢。 Clojure 版本现在只比 java 慢两倍,比以前快 25 倍!
    • 但是注意将 args 作为双数组传递并没有帮助,它提供了 typehint ^doubles 然后按照你的建议使用aget。
    • 我应该编辑它更清楚:它需要传递双数组和提示参数。
    【解决方案2】:

    您是否尝试添加?

    (set! *unchecked-math* true)
    

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

    编辑:@noisesmith 是对的,双数组输入输入会有很大的不同。

    Edit2:在 Alex Miller 的 cmets 之后获得惊人的快速结果。

    (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 毫秒范围内 ...

    【讨论】:

    • 是的,我试过了,但似乎没有任何区别。
    • 顺便说一句,你得到了什么表演?在 Clojure 与 Java 中?
    • 没有类型提示 Clojure fn 花费了 25 微秒,而 java 花费了 500 纳秒。使用 typehints Clojure 需要 1 微秒。
    • 其他一些建议:
    • 一些小建议:1) 使用 alength 而不是 count(避免拆箱结果) 2) 键入提示将余弦相似性返回为双精度(避免在返回时装箱)3) 时间与类似(dotimes [_ 30] (time (cosine-similarity ma mb))) 来查看 jit 预热(或使用标准)。
    猜你喜欢
    • 2015-06-12
    • 1970-01-01
    • 2017-01-24
    • 1970-01-01
    • 1970-01-01
    • 2016-02-15
    • 2014-03-31
    • 2010-12-17
    • 2013-05-08
    相关资源
    最近更新 更多