Clojure 性能 tips

原文章写在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))))))

 

 

你可能感兴趣的:(Clojure 性能 tips)