Clojure世界:如何做性能测试
2023-03-14 22:30:47 时间
我们经常需要在程序中测量某段代码的性能,或者某个函数的性能,在Java中,我们可能简单地循环调用某个方法多少次,然后利用System.currentTimeMillis()方法测量下时间。在Ruby中,一般都是用Benchmark module做测试,提供了更详细的报告信息。
同样,在Clojure里你可以做这些事情,你仍然可以使用System.currentTimeMillis()来测量运行时间,例如:
定义两个求和函数sum1和sum2,一个是利用reduce,一个是自己写loop,然后写了个bench函数循环一定次数执行sum函数并给出执行时间,利用System.currentTimeMillis()方法。显然sum1比sum2快了一倍多。为什么更快?这不是我们的话题,有兴趣可以自己看reduce函数的实现。
除了用System.currentTimeMillis()这样的java方式测量运行时间外,clojure还提供了time宏来包装这一切:
time宏用的不是currentTimeMillis方法,而是JDK5引入的nanoTime方法更精确。重写bench函数:
这样的测试仍然是比较粗糙的,真正的性能测试需要考虑到JVM JIT、warm up以及gc带来的影响,例如我们可能需要预先执行函数多少次来让JVM“预热”这些代码。庆幸的是clojure世界里有一个开源库Criterium帮你自动搞定这一切,它的项目主页也在github上:https://github.com/hugoduncan/criterium
首先在你的项目里添加criterium依赖:
接下来引用criterium.core这个ns,因为criterium主要宏也叫bench,因此我们原来的bench函数不能用了,换个名字叫bench-sum:
调用criterium的bench宏执行测试,使用with-progress-reporting宏包装测试代码并汇报测试进展,测试进展会打印在标准输出上。请注意,我这里并没有利用dotimes做循环测试,因为criterium会自己计算应该运行的循环次数,我们并不需要明确指定,测试下结果:
同样,在Clojure里你可以做这些事情,你仍然可以使用System.currentTimeMillis()来测量运行时间,例如:
user=> (defn sum1 [& args] (reduce + 0 args))
#'user/sum1
user=> (defn sum2 [& args]
(loop [rt 0
args args]
(if args
(recur (+ rt (first args)) (next args))
rt)))
#'user/sum2
user=> (defn bench [sum n]
(let [start (System/currentTimeMillis)
nums (range 0 (+ n 1))]
(dotimes [_ n] (apply sum nums))
(println (- (System/currentTimeMillis) start))))
user=> (bench sum1 10000)
1818
nil
user=> (bench sum2 10000)
4220
nil
#'user/sum1
user=> (defn sum2 [& args]
(loop [rt 0
args args]
(if args
(recur (+ rt (first args)) (next args))
rt)))
#'user/sum2
user=> (defn bench [sum n]
(let [start (System/currentTimeMillis)
nums (range 0 (+ n 1))]
(dotimes [_ n] (apply sum nums))
(println (- (System/currentTimeMillis) start))))
user=> (bench sum1 10000)
1818
nil
user=> (bench sum2 10000)
4220
nil
定义两个求和函数sum1和sum2,一个是利用reduce,一个是自己写loop,然后写了个bench函数循环一定次数执行sum函数并给出执行时间,利用System.currentTimeMillis()方法。显然sum1比sum2快了一倍多。为什么更快?这不是我们的话题,有兴趣可以自己看reduce函数的实现。
除了用System.currentTimeMillis()这样的java方式测量运行时间外,clojure还提供了time宏来包装这一切:
user=> (doc time)
-------------------------
clojure.core/time
([expr])
Macro
Evaluates expr and prints the time it took. Returns the value of
expr.
nil
-------------------------
clojure.core/time
([expr])
Macro
Evaluates expr and prints the time it took. Returns the value of
expr.
nil
time宏用的不是currentTimeMillis方法,而是JDK5引入的nanoTime方法更精确。重写bench函数:
user=> (defn bench [sum n]
(time (dotimes [_ n] (apply sum (range 0 (+ n 1))))))
#'user/bench
user=> (bench sum1 10000)
"Elapsed time: 5425.074 msecs"
nil
user=> (bench sum2 10000)
"Elapsed time: 7893.412 msecs"
nil
尽管精度不一致,仍然可以看出来sum1比sum2快。(time (dotimes [_ n] (apply sum (range 0 (+ n 1))))))
#'user/bench
user=> (bench sum1 10000)
"Elapsed time: 5425.074 msecs"
nil
user=> (bench sum2 10000)
"Elapsed time: 7893.412 msecs"
nil
这样的测试仍然是比较粗糙的,真正的性能测试需要考虑到JVM JIT、warm up以及gc带来的影响,例如我们可能需要预先执行函数多少次来让JVM“预热”这些代码。庆幸的是clojure世界里有一个开源库Criterium帮你自动搞定这一切,它的项目主页也在github上:https://github.com/hugoduncan/criterium
首先在你的项目里添加criterium依赖:
:dependencies [[org.clojure/clojure "1.3.0"]
[criterium "0.2.0"]])
[criterium "0.2.0"]])
接下来引用criterium.core这个ns,因为criterium主要宏也叫bench,因此我们原来的bench函数不能用了,换个名字叫bench-sum:
user=> (use 'criterium.core)
nil
user=> (defn bench-sum [sum n]
(with-progress-reporting (bench (apply sum (range 0 (+ 1 n))) :verbose)))
#'user/bench-sum
nil
user=> (defn bench-sum [sum n]
(with-progress-reporting (bench (apply sum (range 0 (+ 1 n))) :verbose)))
#'user/bench-sum
调用criterium的bench宏执行测试,使用with-progress-reporting宏包装测试代码并汇报测试进展,测试进展会打印在标准输出上。请注意,我这里并没有利用dotimes做循环测试,因为criterium会自己计算应该运行的循环次数,我们并不需要明确指定,测试下结果:
user=> (bench-sum sum1 10000)
Cleaning JVM allocations![](http://www.blogjava.net/Images/dot.gif)
Warming up for JIT optimisations![](http://www.blogjava.net/Images/dot.gif)
Estimating execution count![](http://www.blogjava.net/Images/dot.gif)
Running with sample-count 60 exec-count 1417
Checking GC![](http://www.blogjava.net/Images/dot.gif)
Cleaning JVM allocations![](http://www.blogjava.net/Images/dot.gif)
Finding outliers![](http://www.blogjava.net/Images/dot.gif)
Bootstrapping![](http://www.blogjava.net/Images/dot.gif)
Checking outlier significance
x86_64 Mac OS X 10.7.3 4 cpu(s)
Java HotSpot(TM) 64-Bit Server VM 20.4-b02-402
Runtime arguments: -Dclojure.compile.path=/Users/apple/programming/avos/logdashboard/test/classes -Dtest.version=1.0.0-SNAPSHOT -Dclojure.debug=false
Evaluation count : 85020
Execution time mean : 722.730169 us 95.0% CI: (722.552670 us, 722.957586 us)
Execution time std-deviation : 1.042966 ms 95.0% CI: (1.034972 ms, 1.054015 ms)
Execution time lower ci : 692.122089 us 95.0% CI: (692.122089 us, 692.260198 us)
Execution time upper ci : 768.239944 us 95.0% CI: (768.239944 us, 768.305222 us)
Found 2 outliers in 60 samples (3.3333 %)
low-severe 2 (3.3333 %)
Variance from outliers : 25.4066 % Variance is moderately inflated by outliers
nil
user=> (bench-sum sum2 10000)
Cleaning JVM allocations![](http://www.blogjava.net/Images/dot.gif)
Warming up for JIT optimisations![](http://www.blogjava.net/Images/dot.gif)
Estimating execution count![](http://www.blogjava.net/Images/dot.gif)
Running with sample-count 60 exec-count 917
Checking GC![](http://www.blogjava.net/Images/dot.gif)
Cleaning JVM allocations![](http://www.blogjava.net/Images/dot.gif)
Finding outliers![](http://www.blogjava.net/Images/dot.gif)
Bootstrapping![](http://www.blogjava.net/Images/dot.gif)
Checking outlier significance
x86_64 Mac OS X 10.7.3 4 cpu(s)
Java HotSpot(TM) 64-Bit Server VM 20.4-b02-402
Runtime arguments: -Dclojure.compile.path=/Users/apple/programming/avos/logdashboard/test/classes -Dtest.version=1.0.0-SNAPSHOT -Dclojure.debug=false
Evaluation count : 55020
Execution time mean : 1.070884 ms 95.0% CI: (1.070587 ms, 1.071136 ms)
Execution time std-deviation : 1.057659 ms 95.0% CI: (1.050688 ms, 1.062877 ms)
Execution time lower ci : 1.024195 ms 95.0% CI: (1.024164 ms, 1.024195 ms)
Execution time upper ci : 1.145664 ms 95.0% CI: (1.145664 ms, 1.145741 ms)
Found 1 outliers in 60 samples (1.6667 %)
low-severe 1 (1.6667 %)
Variance from outliers : 19.0208 % Variance is moderately inflated by outliers
nil
Cleaning JVM allocations
![](http://www.blogjava.net/Images/dot.gif)
Warming up for JIT optimisations
![](http://www.blogjava.net/Images/dot.gif)
Estimating execution count
![](http://www.blogjava.net/Images/dot.gif)
Running with sample-count 60 exec-count 1417
Checking GC
![](http://www.blogjava.net/Images/dot.gif)
Cleaning JVM allocations
![](http://www.blogjava.net/Images/dot.gif)
Finding outliers
![](http://www.blogjava.net/Images/dot.gif)
Bootstrapping
![](http://www.blogjava.net/Images/dot.gif)
Checking outlier significance
x86_64 Mac OS X 10.7.3 4 cpu(s)
Java HotSpot(TM) 64-Bit Server VM 20.4-b02-402
Runtime arguments: -Dclojure.compile.path=/Users/apple/programming/avos/logdashboard/test/classes -Dtest.version=1.0.0-SNAPSHOT -Dclojure.debug=false
Evaluation count : 85020
Execution time mean : 722.730169 us 95.0% CI: (722.552670 us, 722.957586 us)
Execution time std-deviation : 1.042966 ms 95.0% CI: (1.034972 ms, 1.054015 ms)
Execution time lower ci : 692.122089 us 95.0% CI: (692.122089 us, 692.260198 us)
Execution time upper ci : 768.239944 us 95.0% CI: (768.239944 us, 768.305222 us)
Found 2 outliers in 60 samples (3.3333 %)
low-severe 2 (3.3333 %)
Variance from outliers : 25.4066 % Variance is moderately inflated by outliers
nil
user=> (bench-sum sum2 10000)
Cleaning JVM allocations
![](http://www.blogjava.net/Images/dot.gif)
Warming up for JIT optimisations
![](http://www.blogjava.net/Images/dot.gif)
Estimating execution count
![](http://www.blogjava.net/Images/dot.gif)
Running with sample-count 60 exec-count 917
Checking GC
![](http://www.blogjava.net/Images/dot.gif)
Cleaning JVM allocations
![](http://www.blogjava.net/Images/dot.gif)
Finding outliers
![](http://www.blogjava.net/Images/dot.gif)
Bootstrapping
![](http://www.blogjava.net/Images/dot.gif)
Checking outlier significance
x86_64 Mac OS X 10.7.3 4 cpu(s)
Java HotSpot(TM) 64-Bit Server VM 20.4-b02-402
Runtime arguments: -Dclojure.compile.path=/Users/apple/programming/avos/logdashboard/test/classes -Dtest.version=1.0.0-SNAPSHOT -Dclojure.debug=false
Evaluation count : 55020
Execution time mean : 1.070884 ms 95.0% CI: (1.070587 ms, 1.071136 ms)
Execution time std-deviation : 1.057659 ms 95.0% CI: (1.050688 ms, 1.062877 ms)
Execution time lower ci : 1.024195 ms 95.0% CI: (1.024164 ms, 1.024195 ms)
Execution time upper ci : 1.145664 ms 95.0% CI: (1.145664 ms, 1.145741 ms)
Found 1 outliers in 60 samples (1.6667 %)
low-severe 1 (1.6667 %)
Variance from outliers : 19.0208 % Variance is moderately inflated by outliers
nil
这个报告是不是相当专业?不是搞统计还不一定读的懂。大概解读下,sample-count是取样次数,默认是60次,exec-count是测试的执行次数(不包括前期warm up和JIT在内),CI是可信区间,这里取95%,Execution time mean是平均执行时间,而lower和upper是测试过程中执行时间的最小和最大值。而outliers是这一组测试中的异常值,比如执行sum1测试发现了2组异常结果。从结果来看,sum1的平均执行时间是722微秒,而sum2的平均执行时间是1.07毫秒,因此还是sum1更快一些。
总结下,如果只是在开发过程中做一些小块代码的简单测试,可以直接利用内置的time宏,如果你希望做一次比较标准的性能测试,那么就应该利用criterium这个优秀的开源库。
文章转自庄周梦蝶 ,原文发布时间2012-03-22
相关文章
- 在 Go 里用 CGO?这 7 个问题你要关注!
- 9款优秀的去中心化通讯软件 Matrix 的客户端
- 求职数据分析,项目经验该怎么写
- 在OKR中,我看到了数据驱动业务的未来
- 火山引擎云原生大数据在金融行业的实践
- OpenHarmony富设备移植指南(二)—从postmarketOS获取移植资源
- 《数据成熟度指数》报告:64%的企业领袖认为大多数员工“不懂数据”
- OpenHarmony 小型系统兼容性测试指南
- 肯睿中国(Cloudera):2023年企业数字战略三大趋势预测
- 适用于 Linux 的十大命令行游戏
- GNOME 截图工具的新旧截图方式
- System76 即将推出的 COSMIC 桌面正在酝酿大变化
- 2GB 内存 8GB 存储即可流畅运行,Windows 11 极致精简版系统 Tiny11 发布
- 迎接 ecode:一个即将推出的具有全新图形用户界面框架的现代、轻量级代码编辑器
- loongarch架构介绍(三)—地址翻译
- Go 语言怎么解决编译器错误“err is shadowed during return”?
- 敏捷:可能被开发人员遗忘的部分
- Denodo预测2023年数据管理和分析的未来
- 利用数据推动可持续发展
- 在 Vue3 中实现 React 原生 Hooks(useState、useEffect),深入理解 React Hooks 的