Clojure中多方法与cond的表现

时间:2015-02-18 06:10:06

标签: clojure

多方法比协议慢,应该尝试使用协议来解决问题,即使使用多方法提供了更灵活的解决方案。 那么cond和多方法的情况如何呢?它们可用于解决同样的问题但我的猜测是多方法与cond相比具有巨大的性能开销。如果是这样,我为什么要使用multimethod而不是cond

3 个答案:

答案 0 :(得分:7)

多种方法允许开放式扩展;其他人可以在任意表达式上扩展你的多方法调度。 Cond表达式被其他人甚至您自己的代码关闭以扩展。

如果你只是想对条件逻辑采取行动,那么cond就是你要走的路。如果您想要进行更复杂的调度,或者对具有不同行为的多种类型的数据应用函数,那么多方法可能更合适。

答案 1 :(得分:6)

为什么要在测量时担心?

以下是使用criterium库的基准示例。 CondMulti-methods代码取自http://blog.8thlight.com/myles-megyesi/2012/04/26/polymorphism-in-clojure.html

警告这只是比较multimethodcond效果的基准测试示例。下面的结果 - 显示cond的效果优于multimethod,不能在实践中推广到各种用法。您可以将此基准测试方法用于您自己的代码。

;; cond
(defn convert-cond [data]
   (cond
     (nil? data)
       "null"
     (string? data)
       (str "\"" data "\"")
     (keyword? data)
       (convert-cond (name data))
     :else
     (str data)))


(bench (convert-cond "yolo"))

Evaluation count : 437830380 in 60 samples of 7297173 calls.

             Execution time mean : 134.822430 ns
    Execution time std-deviation : 1.134226 ns
   Execution time lower quantile : 133.066750 ns ( 2.5%)
   Execution time upper quantile : 137.077603 ns (97.5%)
                   Overhead used : 1.893383 ns

Found 2 outliers in 60 samples (3.3333 %)
    low-severe   2 (3.3333 %)
 Variance from outliers : 1.6389 % Variance is slightly inflated by outliers

;; multimethod
(defmulti convert class)

(defmethod convert clojure.lang.Keyword [data]
  (convert (name data)))

(defmethod convert java.lang.String [data]
   (str "\"" data "\""))

(defmethod convert nil [data]
   "null")

 (defmethod convert :default [data]
   (str data))

(bench (convert "yolo"))
Evaluation count : 340091760 in 60 samples of 5668196 calls.

             Execution time mean : 174.225558 ns
    Execution time std-deviation : 1.824118 ns
   Execution time lower quantile : 170.841203 ns ( 2.5%)
   Execution time upper quantile : 177.465794 ns (97.5%)
                   Overhead used : 1.893383 ns
nil

答案 2 :(得分:3)

为了跟进@AlexMiller评论,我尝试使用更多随机数据进行基准测试,并添加了协议实现(还添加了一个类型 - 整数 - 用于不同的方法)。

(defprotocol StrConvert
  (to-str [this]))

(extend-protocol StrConvert
  nil
  (to-str [this] "null")
  java.lang.Integer
  (to-str [this] (str this))
  java.lang.String
  (to-str [this] (str "\"" this "\""))
  clojure.lang.Keyword
  (to-str [this] (to-str (name this)))
  java.lang.Object
  (to-str [this] (str this)))

data包含10000个随机整数序列,随机转换为Stringnilkeywordvector

(let [fns [identity            ; as is (integer)
           str                 ; stringify
           (fn [_] nil)        ; nilify
           #(-> % str keyword) ; keywordize
           vector]             ; vectorize
      data (doall (map #(let [f (rand-nth fns)] (f %))
                       (repeatedly 10000 (partial rand-int 1000000))))]
  ;; print a summary of what we have in data
  (println (map (fn [[k v]] [k (count v)]) (group-by class data)))
  ;; multimethods
  (c/quick-bench (dorun (map convert data)))
  ;; cond-itionnal
  (c/quick-bench (dorun (map convert-cond data)))
  ;; protocols
  (c/quick-bench (dorun (map to-str data))))

结果是data包含:

([clojure.lang.PersistentVector 1999] [clojure.lang.Keyword 1949]
 [java.lang.Integer 2021] [java.lang.String 2069] [nil 1962])
  • Multimethods:6.26 ms
  • Cond-itionnal:5.18 ms
  • 协议:6.04毫秒

我肯定会建议@DanielCompton:设计比每个方法看起来对的纯粹表现更重要,至少在这个例子中。