posts - 11, comments - 9, trackbacks - 0, articles - 0
  BlogJava :: 首页 :: 新随笔 :: 联系 :: 聚合  :: 管理

Clojure 性能 tips(翻译)

Posted on 2012-07-29 14:15 steven.cui 阅读(1911) 评论(2)  编辑  收藏 所属分类: clojure

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

 

 


评论

# re: Clojure 性能 tips(翻译)  回复  更多评论   

2014-01-23 09:32 by Syeerzy
在我的笔记本电脑上测试, 性能比上面的数字平均大约高 200 倍左右.
而且(注意我改了数字,我的重复次数是上文的100倍)

user=> (time (dotimes [_ 1e9] (+ 2 4 5)))
"Elapsed time: 318.793 msecs"
nil
user=> (time (dotimes [_ 1e9] (+ 2 (+ 4 5))))
"Elapsed time: 322.425 msecs"
nil
user=> (time (dotimes [i 1e9] (== i i)))
"Elapsed time: 315.532 msecs"
nil
user=> (time (dotimes [i 1e9] (= i i)))
"Elapsed time: 316.966 msecs"
nil

强制设置类型,使用二元运算符,使用==代替= 等操作几乎没有带来可以观察到的性能提升,而不是文中说的几十倍.

另外,避免vectors的destructing binding实测提升性能70%左右(而不是文中的20几倍)
优先使用本地变量实测提升性能20%-30%(而不是文中的12倍)


基本上所有文中说的提升性能的操作都是不划算的. 此文有严重误导倾向. 所有测试都照文中给的代码, 并且测试20次以上.

# re: Clojure 性能 tips(翻译)  回复  更多评论   

2014-02-13 22:50 by steven.cui
@Syeerzy
具体性能提高多少可能跟jvm版本以及clj版本有区别,这个应该还是在clojure1.3之前的文章,现在的版本估计已经足够聪明了,还是那句话过度优化和过早优化都是恶魔。@Syeerzy

只有注册用户登录后才能发表评论。


网站导航: