【问题标题】:Performance of multimethod vs cond in ClojureClojure 中多方法与 cond 的性能
【发布时间】:2015-02-18 06:10:05
【问题描述】:

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

【问题讨论】:

    标签: clojure


    【解决方案1】:

    多方法允许开放扩展;其他人可以通过在其源中添加新的defmethods 来扩展您对任意表达式的多方法调度。 Cond 表达式在不编辑 cond 源代码的情况下被其他人甚至您自己的代码关闭。

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

    【讨论】:

    • 但是 multimethod 与 cond 相比引入了多少开销?
    • 同意。使用最适合您的特定任务的方法,以后再担心性能。
    • 你能澄清一下在这种情况下你所说的“开放扩展”是什么意思吗?在我看来,条件表达式和多方法都可以编辑/扩展,所以我不明白为什么后者是“开放的”而前者不是。
    • 我已经对此进行了调整以澄清。
    【解决方案2】:

    什么时候可以测量?

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

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

    ;;条件 (定义转换条件 [数据] (条件 (无?数据) “空值” (字符串?数据) (str "\"" 数据 "\"") (关键字?数据) (转换条件(名称数据)) :别的 (str 数据))) (长凳 (convert-cond "yolo")) 评估计数:7297173 个调用的 60 个样本中的 437830380 个。 平均执行时间:134.822430 ns 执行时间标准偏差:1.134226 ns 执行时间下分位数:133.066750 ns (2.5%) 执行时间上分位数:137.077603 ns (97.5%) 使用的开销:1.893383 ns 在 60 个样本中发现 2 个异常值 (3.3333 %) 低重度 2 (3.3333 %) 异常值的方差:1.6389 % 异常值略微夸大了方差 ;;多方法 (defmulti 转换类) (defmethod 转换 clojure.lang.Keyword [数据] (转换(名称数据))) (defmethod 转换 java.lang.String [数据] (str "\"" 数据 "\"")) (defmethod 转换 nil [数据] “空值”) (定义方法转换:默认 [数据] (str 数据)) (长凳(转换“yolo”)) 评估计数:5668196 个调用的 60 个样本中的 340091760。 平均执行时间:174.225558 ns 执行时间标准偏差:1.824118 ns 执行时间下分位数:170.841203 ns (2.5%) 执行时间上分位数:177.465794 ns (97.5%) 使用的开销:1.893383 ns 零

    【讨论】:

    • 你不应该相信这个基准——你只是在转换一种类型,在这两种情况下都会导致最优的代码路径(多方法中的单态调用站点)。最好使用输入类型的随机混合进行测试(理想情况下与实际代码中的情况相匹配)。
    【解决方案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])
    
    • 多方法:6.26 毫秒
    • 条件:5.18 毫秒
    • 协议:6.04 毫秒

    我当然会建议 @DanielCompton :设计比每种方法看起来配对的纯粹性能更重要,至少在这个例子中。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-12-25
      • 1970-01-01
      • 1970-01-01
      • 2013-05-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多