年前一篇blog提过,写了一个stm-profiler用于统计clojure STM的运行状况,放在了github上:
https://github.com/killme2008/stm-profiler
STM的事务在遇到写冲突(多个事务写同一个ref的时候)就会回滚事务并重试,通过stm-profiler你可以查看事务的重试次数,重试原因,以及每个reference的使用情况。使用很简单,在lein的project.clj引用stm-profiler:
[stm-profiler "1.0.2-SNAPSHOT"]
注意,目前stm profiler仅支持clojure 1.3。
我们写一个简单例子:
(use 'stm)
(def a (ref 1))
(def b (ref 2))
(dotimes [_ 100] (future (dosync (alter a + 1) (alter b - 1))))
(Thread/sleep 1000)
(prn @a)
(prn @b)
(Thread/sleep 1000)
(prn "stm statistics" (stm-stats))
(prn "reference a statistics" (ref-stats a))
(prn "reference b statistics" (ref-stats b))
定义了两个ref:a和b,然后用future启动100个线程并发地发起同一个事务操作,对a加一,对b减一。最后打印a和b的值,使用stm-stats函数获取stm的统计信息并打印,使用ref-stats获取a和b两个reference的统计信息并打印。
运行这个例子,在启动的时候会有些警告信息,忽略即可(主要是因为stm profiler重新定义了一些跟STM相关的函数和宏,如dosync等,但是仅仅是添加了统计功能,并没有修改他们原本的功能)。
在我机器上的一次输出:
101
-98
"stm statistics" {"(alter a + 1)(alter b - 1)" {:not-running 11, :average-retry 5, :total-cost 1233, :get-fault 44, :barge-fail 224, :change-committed 227, :total-times 100, :average-cost 12}}
"reference a statistics" {"(alter a + 1)(alter b - 1)" {:alter 609, :get-fault 44, :barge-fail 224, :change-committed 227}}
"reference b statistics" {"(alter a + 1)(alter b - 1)" {:alter 114, :not-running 11}}
a和b的结果都没问题。重点看打印的统计信息,(stm-stats)的输出结果是:
{"(alter a + 1)(alter b - 1)" {:not-running 11, :average-retry 5, :total-cost 1233, :get-fault 44, :barge-fail 224, :change-committed 227, :total-times 100, :average-cost 12}}
这个结果是一个map,key是事务的form,而value就是该form的统计信息,也是一个map,具体各项的含义如下:
total-cost
|
所有事务的总耗时
|
100个事务耗时1233毫秒
|
total-times
|
事务运行次数
|
100次
|
average-cost
|
平均每个事务耗时
|
平均一个事务耗时12毫秒
|
average-retry
|
平均每个事务的重试次数 |
平均每个事务重试了5次才成功 |
not-running |
当前事务不处于running状态,可能是被其他事务打断(barge),需要重试 |
因为not-running的原因重试了11次 |
get-fault
|
读取ref值的时候没有找到read point之前的值,被认为是一次读错误,需要重试
|
因为读ref错误重试了44次
|
barge-fail |
打断其他事务失败次数,需要重试 |
尝试打断其他事务失败而重试了224次 |
change-committed |
在本事务read point之后有ref值获得提交,则需要重试
|
因为ref值被其他事务提交而重试了227次 |
从输出结果来看,这么简单的一个事务操作,每次事务要成功平均都需要经过5次的重试,最大的原因是因为ref的值在事务中被其他事务更改了,或者尝试打断其他正在运行的事务失败而重试。关于clojure STM的具体原理推荐看这篇文章《
Software transactional memory》。STM不是完美的,事务重试和保存每个reference的历史版本的代价都不低。
再看
(ref-stats a)的输出:
{"(alter a + 1)(alter b - 1)" {:alter 609, :get-fault 44, :barge-fail 224, :change-committed 227}}
可以看到a在所有事务中的统计信息,返回的结果同样是个map,key是使用了a的事务,value是具体的统计信息。各项的含义类似上表,不过这里精确到了具体的reference。其中alter项是指对a调用alter函数了609次。ref-stats会输出所有在事务中调用了a的函数的调用次数。
通过stm profiler你可以分析具体每个事务的执行状况,甚至每个reference的运行状况,查找热点事务和热点reference等。stm-profiler还不完善,目前还不支持1.2(1.4测试是可以的)。希望有兴趣的朋友加入进来一起完善。
转载请注明出处:
http://www.blogjava.net/killme2008/archive/2012/02/09/369694.html