Clojure世界: STM的统计

   年前一篇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测试是可以的)。希望有兴趣的朋友加入进来一起完善。

文章转自庄周梦蝶  ,原文发布时间2012-02-09

时间: 2024-09-17 04:50:12

Clojure世界: STM的统计的相关文章

Clojure世界:如何做性能测试

  我们经常需要在程序中测量某段代码的性能,或者某个函数的性能,在Java中,我们可能简单地循环调用某个方法多少次,然后利用System.currentTimeMillis()方法测量下时间.在Ruby中,一般都是用Benchmark module做测试,提供了更详细的报告信息.     同样,在Clojure里你可以做这些事情,你仍然可以使用System.currentTimeMillis()来测量运行时间,例如: user=> (defn sum1 [& args] (reduce + 

Clojure世界:静态代码分析

    Java世界里有findbugs这样的神器,可以让你避免很多"简单愚蠢"的bug.同样,Clojure世界里也有相应的替代品,这就是今天要介绍的kibit.不过kibit现在还比较年轻,判断的规则较少,但是已经可以使用起来做clojure代码的静态检查. 项目主页:https://github.com/jonase/kibit 使用: 1.安装lein插件: lein plugin install jonase/kibit 0.0.2 2.在项目的根目录运行 lein kibi

Clojure世界:API文档生成

    继续Clojure世界之旅,介绍下我今天的探索成果,使用clojure生成clojure项目的API文档.在java里,我们是利用javadoc生成API文档,各种build工具都提供了集成,例如maven和ant都提供了javadoc插件或者task.在Clojure世界里,同样有一系列工具帮助你从源码中自动化生成API文档.今天主要介绍三个工具.不过我不会介绍怎么在clojure里写doc,具体怎么做请看一些开源项目,或者直接看clojure.core的源码.     首先是codo

专家谈我国GDP世界第二:统计方法存在失真

中国GDP增速一年一个台阶.(新华社) 新闻由头 随着日本近日公布上半年和二季度国内生产总值(GDP)数据,中国GDP总量是否超越日本成为世界第二大经济体再度成为议论焦点. 8月16日,日本内阁府公布的数据显示,日本第二季度国内生产总值(GDP)为1.288万亿美元,同期中国GDP总值为1.337万亿美元. 早在8月2日,中国央行副行长.国家外汇管理局局长易纲接受采访时就透露,中国实际上已是世界第二大经济体,这是中国官员首次指出中国经济成为"世界第二". 在很多西方媒体看来,中国GDP

Clojure世界:使用rlwrap增强REPL

   Clojure的REPL非常方便,可以随时随地试验你的想法,REPL是read-eval-print-loop的简称.默认clojure.contrib有带一个shell脚本来启动REPL,具体看这里.你也可以用JLine来增强REPL: java -cp "%CLOJURE_DIR%\jline-VERSION.jar;%CLOJURE_JAR%" jline.ConsoleRunner clojure.main     不过,其实你还可以用rlwrap这个GNU库来增强clo

Clojure世界:文件IO

   文件读写是日常编程中最经常使用的操作之一.这篇blog将大概介绍下Clojure里对文件操作的常用类库.     首先介绍标准库clojure.java.io,这是最经常用的IO库,定义了常见的IO操作.     首先,直接看一个例子,可以熟悉下大多数常用的函数: (ns io   (:use [clojure.java.io])) ;;file函数,获取一个java.io.File对象 (def f (file "a.txt")) ;;拷贝文件使用copy (copy f (f

Clojure世界:利用HouseMD诊断clojure

  HouseMD是淘宝的聚石写的一个非常优秀的Java进程运行时诊断和调试工具,如果你接触过btrace,那么HouseMD也许你应该尝试下,它比btrace更易用,不需要写脚本,类似strace的方式attach到jvm进程做跟踪调试.     基本的安装和使用请看这篇文档<UserGuide>,恕不重复.以下内容都假设你正确安装了housemd.     本文主要介绍下怎么用housemd诊断跟踪clojure进程.Clojure的java实现也是跑在JVM里,当然也可以用housemd

Clojure世界:单元测试

    单元测试也是一个开发中最常见的需求,在Java里我们用JUnit或者TestNG,在clojure里也内置了单元测试的库.标准库的clojure.test,以及第三方框架midje.这里我将主要介绍clojure.test这个标准库,midje是个更加强大的测试框架,广告下,midje的介绍在第二次cn-clojure聚会上将有个Topic,我就不画蛇添足了.通常来说,clojure.test足够让你对付日常的测试.     首先看一个最简单的例子,定义一个函数square来计算平方,然

Clojure世界:日志管理——clojure.tools.logging

   处理日志是任何一个产品级的程序都需要仔细处理的模块.在Java中,我们经常使用的是log4j就是一个日志框架.在clojure里,同样有一套日志框架--clojure.tools.logging,它不仅提供了常用的日志输出功能,还屏蔽了Java各种日志框架之间的差异,如slf4j,commons-logging,log4j,java.util.logging等,让你可以透明地使用这些框架来处理日志. 名称:clojure.tools.logging 主页:https://github.co