Clojure通勤和改变性能
这里有一个稍加修改的例子 样本输出为Clojure通勤和改变性能,clojure,stm,Clojure,Stm,这里有一个稍加修改的例子 样本输出为 ([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" 我不是交换唯一的数字,而是将数字从一个向量元素转移到另一个向量元素 此操作可以假定为可交换的,因此还有另一个测试-除了使用communion而不是alter (time (run commute 100 10 10
([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"
我不是交换唯一的数字,而是将数字从一个向量元素转移到另一个向量元素
此操作可以假定为可交换的,因此还有另一个测试-除了使用communion
而不是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秒
,而第二个示例需要3秒
但如前所述
通勤
是alter的优化版本,适用于那些事情的顺序并不重要的时候
在这种简单的情况下,它需要更多的时间来完成相同的工作,如何对其进行优化?
通勤
的目的是什么?我在使用alter
和通勤
运行示例时,曾监视所涉及的clojure.core
函数
alter
通勤
如果我对结果的解释是正确的,则在每个功能上花费的累计时间表明,通勤
实际上比alter
快。为了并行运行代码而需要执行的所有其他操作的开销似乎都会影响性能
对代码进行基准测试非常棘手,使用时间
有时会产生误导。VisualVm提供的信息甚至可能不是最终的词汇,但分析和使用工具(如)可能是确保结果可信的最佳方式
另一个重要的事实是,在dosync
块内执行的操作不会花费那么长的时间,因此即使其中一个操作重试,所涉及的额外时间也不会太长。在dosync
中添加一点延迟会使重试(alter
)和不重试(通勤
)之间的区别更加明显
(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
需要丢弃结果的情况下不必要地重新运行区块内的代码
未指定通勤
和alter
实现之间的常数因子开销,因此您在这里看到的内容不违反Clojure规范的任何部分。这就是说,随着dosync
块中单个事务所花费的时间的增加,当您可以使用communion
时,使用alter
的惩罚也会增加
一般而言:
- 微基准是邪恶的(从某种意义上说,它们鼓励不适合现实世界使用的坏做法)。注意真实场景中的性能行为,而不是人为设计的测试用例
- 尽可能在Clojure的STM中使用
通勤
通勤上使用alter
吗?这是个好问题。但我不敢概括,我所知道的解决这类问题的最好方法是尝试一些事情并衡量结果。如果是我,我会根据执行的操作的性质(可交换和不可交换)进行选择,只有当它成为瓶颈时,才尝试使用另一个。@Odomontois,另一件需要记住的事情是Clojure实现可以在将来优化可交换
情况。您为它提供了更多有关代码的信息,以便它能够更高效地执行某些操作,无论它在任何给定的点发布(对于任何给定的代码块)中是否实际更高效。基于特定于实现的基准编写代码可能在针对某个版本运行时对您有所帮助,但如果您没有在新的Clojure运行时出现时重新进行基准测试。。。
(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"