原文章寫在Google Groups thread里,但是還是值得再說下。
有朋友把Java和Clojure的一些代碼片段放在Clojure Google group里比較,并提到Java的性能要比Clojure快太多了,疑問到底Clojure能不能趕上Java?
在我的一個(gè)開源項(xiàng)目clj-starcraft中,關(guān)于java的性能問題,實(shí)際上也是我始終面對(duì)的,在我寫這篇文章的時(shí),我的Clojure代碼還是慢了Java代碼6倍(Clojure花了70秒解析了1050個(gè)文件,Java則只有12秒)
然而,70秒對(duì)過去的速度而言不算太糟糕,在剛開始的時(shí)候,竟然花了10分鐘來分析1050個(gè)文件。甚至比我用Python實(shí)現(xiàn)的還要慢。
感謝Java的profiler和熱情的Clojure朋友,下面列出了我在提升Clojure性能方面的一些tips:
(set! *warn-on-reflection* true)
這恐怕是最重要的一個(gè)提升:打開這個(gè)設(shè)置將會(huì)警告你在任何一處用到Java反射API的方法和屬性。如你所想,直接調(diào)用永遠(yuǎn)比反射要快,不管哪里Clojure都會(huì)你不能解析這個(gè)方法,你需要自己用type hint方式來避免反射調(diào)用。關(guān)于使用type hint,Clojure官方站點(diǎn)給了一個(gè)如何使用和提速的例子。
修復(fù)所有關(guān)于*warn-on-reflection* 的編譯警告后,我的clj-starcraft從10分鐘降到了3分半。
強(qiáng)制設(shè)置數(shù)據(jù)類型
Clojure可以使用Java的基礎(chǔ)數(shù)據(jù)類型,無論何時(shí)在循環(huán)的時(shí)候,堅(jiān)決考慮將你的值強(qiáng)制轉(zhuǎn)換成基礎(chǔ)類型,這將大幅提高你的性能。基礎(chǔ)數(shù)據(jù)類型在Clojure官方網(wǎng)站有例子和如何進(jìn)行強(qiáng)制轉(zhuǎn)換來提高性能。
使用二元運(yùn)算符
Clojure可以在一行里面支持多個(gè)表達(dá)式,但對(duì)于運(yùn)算操作符,只有在兩個(gè)的時(shí)候才被inlined,如果你發(fā)現(xiàn)自己的運(yùn)算符已經(jīng)超過了兩個(gè),或許該考慮重寫你的代碼讓操作符顯示的成為兩個(gè)。下面請(qǐng)看兩者之間的比較:
user> (time (dotimes [_ 1e7] (+ 2 4 5)))
"Elapsed time: 1200.703487 msecs"
user> (time (dotimes [_ 1e7] (+ 2 (+ 4 5))))
"Elapsed time: 241.716554 msecs"
使用==代替=
使用==比較數(shù)字來代替=,提升性能那是相當(dāng)明顯:
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
在一段循環(huán)種,如果你想為了提升可讀性從vector中傳出值,考慮下標(biāo)訪問來代替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"
優(yōu)先使用本地變量
如果你需要在循環(huán)中查詢一個(gè)值,你或許需要考慮使用本地變量(通過let定義)來代替全局變量。看下兩者的時(shí)間對(duì)比:
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有兩個(gè)profiler工具, -Xprof和-Xrunhprof,找到程序瓶頸而不是瞎猜。
最后說明:
你已經(jīng)注意到,在這些性能提升中,通過調(diào)用百萬量的執(zhí)行來提升了幾百毫秒的性能。所以,不到萬不得已需要提升性能的時(shí)候,沒必要讓你的代碼看起來不夠清晰。
原文地址: http://gnuvince.wordpress.com/2009/05/11/clojure-performance-tips/
最后補(bǔ)充:可以通過指定編譯為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))))))