(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
的目的是什么?
答案 0 :(得分:4)
我使用VisualVM来监控使用clojure.core
和alter
运行示例时涉及的commute
函数。
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"
答案 1 :(得分:2)
了解commute
所做的优化具体是非常重要的: commute
避免在alter
需要的情况下不必要地重新运行块内的代码抛弃结果。
未指定commute
和alter
的实现之间的常量因素开销,因此您在此处看到的内容违反了Clojure规范的任何部分。也就是说 - 随着dosync
块内个别交易所花费的时间增加,使用alter
时使用commute
的代价会增加,同样会增加{/ 1}}。
一般来说:
commute
。