【问题标题】:Clojure commute and alter performanceClojure 通勤和改变性能
【发布时间】:2014-11-11 13:52:14
【问题描述】:

clojure.org/refs 有一点修改的例子

(defn mod-nth [v i f] (assoc v i (f (v i))))
(defn run [oper nvecs nitems nthreads niters]
  (let [vec-refs (vec (map (comp ref vec)
                        (partition nitems (repeat (* nvecs nitems) 0))))
        sum  #(reduce + %)
        swap #(let [v1 (rand-int nvecs)
                    v2 (rand-int nvecs)
                    i1 (rand-int nitems)
                    i2 (rand-int nitems)]
                (dosync
                  (let [temp (nth @(vec-refs v1) i1)]
                    (oper (vec-refs v1) mod-nth i1 inc)
                    (oper (vec-refs v2) mod-nth i2 dec))))
        report #(do
                  (prn (map deref vec-refs))
                  (println "Sum:"
                    (reduce + (map (comp sum deref) vec-refs))))]
    (report)
    (dorun (apply pcalls (repeat nthreads #(dotimes [_ niters] (swap)))))
    (report)))

(time (run alter 100 10 10 100000))

样本输出为

([0 0 0 0 0 0 0 0 0 0] [...])
Sum: 0
([15 -14 -8 57 -26 -12 -49 -29 33 -3] [...])
Sum: 0
"Elapsed time: 1995.938147 msecs"

我不是交换唯一数字,而是将一个向量元素从一个向量元素转移到另一个。

这个操作可以被假定为可交换的,所以还有另一个测试——除了使用commute而不是alter之外,它是相同的

(time (run commute 100 10 10 100000))

带有样例输出

([0 0 0 0 0 0 0 0 0 0] [...])
Sum: 0
([8 48 -10 -41 -17 -32 -4 50 -31 88] [...])
Sum: 0
"Elapsed time: 3141.591517 msecs"

令人惊讶的是,第一个示例大约在 2 seconds 中运行,而第二个示例需要 3 seconds

但正如提到的in this SO answer

commute 是 alter 的优化版本,适用于那些事情顺序真的无关紧要的时代

在这种简单的情况下,它需要更多时间来完成相同的工作,如何对其进行优化? commute的目的是什么?

【问题讨论】:

    标签: clojure stm


    【解决方案1】:

    在同时使用altercommute 运行示例时,我使用VisualVM 来监视所涉及的clojure.core 函数。

    alter

    commute

    如果我对结果的解释是正确的,在每个函数上花费的累计时间表明commute 实际上比alter 快。看来并行运行代码所需执行的所有其他操作的开销是影响性能的那些。

    基准测试代码非常棘手,使用time 有时会产生误导。尽管 VisualVm 提供的信息甚至可能不是终极词汇,但分析和使用诸如 criterium 之类的工具可能是确保结果值得信赖的最佳方式。

    另一个重要的事实是,在dosync 块内完成的操作不会花费那么长时间,因此即使其中一个重试,所涉及的额外时间也不那么重要。在dosync 中添加一点延迟会使重试 (alter) 和不重试 (commute) 之间的差异更加明显。

    (defn mod-nth [v i f] (assoc v i (f (v i))))
    (defn run [oper nvecs nitems nthreads niters]
      (let [vec-refs (vec (map (comp ref vec)
                            (partition nitems (repeat (* nvecs nitems) 0))))
            sum  #(reduce + %)
            swap #(let [v1 (rand-int nvecs)
                        v2 (rand-int nvecs)
                        i1 (rand-int nitems)
                        i2 (rand-int nitems)]
                   (dosync
                     (let [temp (nth @(vec-refs v1) i1)]
                       (Thread/sleep 1)                     ;; This was added
                       (oper (vec-refs v1) mod-nth i1 inc)
                       (oper (vec-refs v2) mod-nth i2 dec))))
            report #(do
                      (prn (map deref vec-refs))
                      (println "Sum:"
                        (reduce + (map (comp sum deref) vec-refs))))]
        (doall (apply pcalls (repeat nthreads #(dotimes [_ niters] 
                                                (swap)))))))
    
    (time (run alter 100 10 10 5000))
    ;= "Elapsed time: 15252.427 msecs"
    (time (run commute 100 10 10 5000))
    ;= "Elapsed time: 13595.399 msecs"
    

    【讨论】:

    • 所以问题是:当事务操作很小时,我不应该使用alter 而不是commute
    • 这是个好问题。不过我不敢一概而论,我知道找出这类东西的最好方法是尝试并衡量结果。如果是我,我会根据所执行操作的性质(可交换和不可交换)进行选择,只有当它成为瓶颈时,才尝试使用另一个。
    • @Odomontois,另外要记住的是,Clojure 实现将来可以优化commute 的情况。您正在向它提供有关您的代码的更多信息以执行某些操作,因此它能够更高效,无论它实际上是在任何给定的发布点更有效(对于任何给定的代码块)与否。在针对一个版本运行时,根据特定于实现的基准编写代码可能会对您有所帮助,但如果您没有在新的 Clojure 运行时出现时重新进行基准测试......
    【解决方案2】:

    了解commute 所做的优化具体是什么很重要:commute 避免在alter 需要丢弃结果的情况下不必要地重新运行块内的代码。

    未指定commutealter 实现之间的常数因子开销,因此您在此处看到的内容不违反Clojure 规范的任何部分。也就是说,随着 dosync 块内单个事务花费的时间增加,当您可以使用 commute 时使用 alter 的惩罚也会增加。

    一般:

    • 微基准是邪恶的(从某种意义上说,它们鼓励不适合实际使用的不良做法)。注意真实场景中的性能行为,而不是人为的测试用例。
    • 尽可能在 Clojure 的 STM 中使用 commute

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-01-19
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-08-01
      相关资源
      最近更新 更多