原文章写在Google Groups thread里,但是还是值得再说下。
有朋友把Java和Clojure的一些代码片段放在Clojure Google group里比较,并提到Java的性能要比Clojure快太多了,疑问到底Clojure能不能赶上Java?
在我的一个开源项目clj-starcraft中,关于java的性能问题,实际上也是我始终面对的,在我写这篇文章的时,我的Clojure代码还是慢了Java代码6倍(Clojure花了70秒解析了1050个文件,Java则只有12秒)
然而,70秒对过去的速度而言不算太糟糕,在刚开始的时候,竟然花了10分钟来分析1050个文件。甚至比我用Python实现的还要慢。
感谢Java的profiler和热情的Clojure朋友,下面列出了我在提升Clojure性能方面的一些tips:
(set! *warn-on-reflection* true)
这恐怕是最重要的一个提升:打开这个设置将会警告你在任何一处用到Java反射API的方法和属性。如你所想,直接调用永远比反射要快,不管哪里Clojure都会你不能解析这个方法,你需要自己用type hint方式来避免反射调用。关于使用type hint,Clojure官方站点给了一个如何使用和提速的例子。
修复所有关于*warn-on-reflection* 的编译警告后,我的clj-starcraft从10分钟降到了3分半。
强制设置数据类型
Clojure可以使用Java的基础数据类型,无论何时在循环的时候,坚决考虑将你的值强制转换成基础类型,这将大幅提高你的性能。基础数据类型在Clojure官方网站有例子和如何进行强制转换来提高性能。
使用二元运算符
Clojure可以在一行里面支持多个表达式,但对于运算操作符,只有在两个的时候才被inlined,如果你发现自己的运算符已经超过了两个,或许该考虑重写你的代码让操作符显示的成为两个。下面请看两者之间的比较:
user> (time (dotimes [_ 1e7] (+ 2 4 5)))
"Elapsed time: 1200.703487 msecs"
user> (time (dotimes [_ 1e7] (+ 2 (+ 4 5))))
"Elapsed time: 241.716554 msecs"
使用==代替=
使用==比较数字来代替=,提升性能那是相当明显:
user> (time (dotimes [i 1e7] (= i i)))
"Elapsed time: 230.797482 msecs"
user> (time (dotimes [i 1e7] (== i i)))
"Elapsed time: 5.143681 msecs"
避免vectors的destructing binding
在一段循环种,如果你想为了提升可读性从vector中传出值,考虑下标访问来代替destructing binding。虽然代码看起来更清晰,但却非常慢。
user> (let [v [1 2 3]]
(time
(dotimes [_ 1e7]
(let [[a b c] v]
a b c))))
"Elapsed time: 537.239895 msecs"
user> (let [v [1 2 3]]
(time
(dotimes [_ 1e7]
(let [a (v 0)
b (v 1)
c (v 2)]
a b c))))
"Elapsed time: 12.072122 msecs"
优先使用本地变量
如果你需要在循环中查询一个值,你或许需要考虑使用本地变量(通过let定义)来代替全局变量。看下两者的时间对比:
user> (time
(do
(def x 1)
(dotimes [_ 1e8]
x)))
"Elapsed time: 372.373304 msecs"
user> (time
(let [x 1]
(dotimes [_ 1e8]
x)))
"Elapsed time: 3.479041 msecs"
如果你想使用本地变量来提升性能,可以考虑下面比较土的式的方式来避免全局变量:
(let [local-x x]
(defn my-fn [a b c]
...))
使用profiler工具:
JVM有两个profiler工具, -Xprof和-Xrunhprof,找到程序瓶颈而不是瞎猜。
最后说明:
你已经注意到,在这些性能提升中,通过调用百万量的执行来提升了几百毫秒的性能。所以,不到万不得已需要提升性能的时候,没必要让你的代码看起来不够清晰。
原文地址: http://gnuvince.wordpress.com/2009/05/11/clojure-performance-tips/
最后补充:可以通过指定编译为static方法来提高性能:
pasting
(defn
^{:static true}
fib
[n]
(loop [a (long 1) b (long 1) i (long 1) r (list 1 1)]
(if (== n i)
r
(recur b (+ a b) (inc i) (conj r (+ a b))))))