??xml version="1.0" encoding="utf-8" standalone="yes"?>
变量和全局环境
Clojure是个很实用的语言Q偶需要将l护和改变数据的倹{她提供?U不同的方式来操作变量:Vars, Refs, Agents, 和Atoms。Vars机制是是指向一个可改变的数据的位置Q你可以为每个线E动态的l定Q制定一个新的存储位|)一个新倹{Vars可以初始化根l定(不是必须?Q绑定的值对于所有线E都是共享的Q但却别的线E就不能重新l定。因此,要么Var可以为每个线E绑定|要么使用根绑定?/p>
下面的special form def 创徏了一个VarQ如果Var不存在和没有l初始化Qvar是不绑定的Q不允许创徏非动态的VarQ必L式指定根l定Q:
user=> (def x)
#'user/x
user=> x
java.lang.IllegalStateException: Var user/x is unbound.
为根值初始化Q如果存在,p再次l定Q?/p>
user=> (def x 1)
#'user/x
user=> x
1
默认情况?定义的时候初始化了根l定Q,Vars是静态的(static)Q但是,建立动态Var的定义可以通过元数据标记的方式Q然后在U程用时通过binding来指定?/p>
user=> (def ^:dynamic x 1)
user=> (def ^:dynamic y 1)
user=> (+ x y)
2
user=> (binding [x 2 y 3]
(+ x y))
5
user=> (+ x y)
2
binding被创建后其他U程是是不可见的。创建的binding可以被赋|也就是在没有d调用堆栈之前可以被上下文讉K。可以在一块代码之前设|matadata标签:dynamic来指?
user=> (def ^:dynamic x 1)
#'user/x
user=> (meta #'x)
{:ns #<Namespace user>, :name x, :dynamic true, :line 30, :file "NO_SOURCE_PATH"}
user=> (binding [x 2] (println x))
2
nil
user=> x
1
user=>
如果你想让函数编译ؓstatic的,q且指定q回|可以看下面的例子(速度提升不少Q关键的调用函数可以采用q种方式加?Q?/p>
(defn fib [n] (if (<= n 1)
1
(+ (fib (dec n)) (fib (- n 2)))))
#'user/fib
(defn ^:static fib2 ^long [^long n]
(if (<= n 1)
1
(+ (fib2 (dec n)) (fib2 (- n 2)))))
#'user/fib2
user=> (time (fib 38))
"Elapsed time: 1831.113 msecs"
63245986
user=> (time (fib2 38))
"Elapsed time: 328.715 msecs"
63245986
user=> (meta (var fib))
{:arglists ([n]), :ns #<Namespace user>, :name fib, :line 1, :file "NO_SOURCE_PATH"}
user=> (meta (var fib2))
{:arglists ([n]), :ns #<Namespace user>, :name fib2, :static true, :line 4, :file "NO_SOURCE_PATH"}
user=>
在上下文中可能需要重定义静态变量,从Clojure1.3开始提?a style="color: #546188; ">with-redefs?a style="color: #546188; ">with-redefs-fnq两个宏来修攏V?/p>
定义函数?a style="color: #546188; ">defn也是Vars的存储方式,也可以在q行时被重定义。这也ؓaop~程带来很多方便Q例如:你可以封装一个类似logging函数l调用的上下文或者或者线E?/p>
(set! var-symbol expr)
Vars指定为special form
当地一个操作符为symbol的时候,它必L全局变量。当前线E绑定的值就是后面的exprQ也是说必LThread-local的才可以Q否则将会抛Z个用set!来设定根l定变量的错误。变量的表达式expr必须有返回倹{?/p>
注意Q你不能赋值给一个函数的参数或者本地绑定,只能是java的字DVars Refs和AgentsQ因些数据在Clojure里可不变的?/p>
使用set为java字段讄|可以查看 Java Interop.
Interning
命名I间l护了每个Var对象的全局W号映射。如果用def定义变量没有在当前的命名I间扑ֈ该符P创Z个,否则使用现有的。创建或者寻扄q程被称作interning。这意味着Q除非Var对象取消映射Q否则Var对象每次被查询,所以请在@环中千万不要引用Var的全局变量Q否则将非常慢,通过let或者binding让全局变量取消映射来提高速度。命名空间在Evaluation中构Z全局环境Q编译器也把所有free symbols当做Vars来解析了?/p>
可以使用阅读?Reader)#’来得到Var对象的内部的倹{?/p>
Non-interned的类型的变量
可以通过with-local-vars来创建non-internedcd的变量,在free symbol解析的时候将不会被发玎ͼq些值只能被手工的访问,但是也可以用作当前线E的变量?/p>
user=> (defn factorial [x]
(with-local-vars [acc 1, cnt x]
(while (> @cnt 0)
(var-set acc (* @acc @cnt))
(var-set cnt (dec @cnt)))
@acc))
#'user/factorial
user=> (factorial 7)
5040
有朋友把Java和Clojure的一些代码片D|在Clojure Google group里比较,q提到Java的性能要比Clojure快太多了Q疑问到底Clojure能不能赶上Java?
在我的一个开源项?a >clj-starcraft中,关于java的性能问题Q实际上也是我始l面对的Q在我写q篇文章的时Q我的Clojure代码q是慢了Java代码6?Clojure׃70U解析了1050个文ӞJava则只?2U?
然而,70U对q去的速度而言不算太糟p,在刚开始的时候,竟然׃10分钟来分?050个文件。甚x我用Python实现的还要慢?/p>
感谢Java的profiler和热情的Clojure朋友Q下面列Z我在提升Clojure性能斚w的一些tips:
(set! *warn-on-reflection* true)
q恐怕是最重要的一个提升:打开q个讄会警告你在M一处用到Java反射API的方法和属性。如你所惻I直接调用永远比反要快,不管哪里Clojure都会你不能解析这个方法,你需要自qtype hint方式来避免反调用。关于用type hintQClojure官方站点l了一个如何用和提速的例子?/p>
修复所有关?strong>*warn-on-reflection* 的编译警告后Q我的clj-starcraft?0分钟降到?分半?/p>
强制讄数据cd
Clojure可以使用Java?a >基础数据cdQ无Z时在循环的时候,坚决考虑你的值强制{换成基础cdQ这大q提高你的性能。基数据cd在Clojure官方|站有例子和如何q行强制转换来提高性能?/p>
使用二元q算W?/strong>
Clojure可以在一行里面支持多个表辑ּQ但对于q算操作W,只有在两个的时候才被inlinedQ如果你发现自己的运符已经过了两个,或许该考虑重写你的代码让操作符昄的成Z个。下面请看两者之间的比较Q?/p>
user> (time (dotimes [_ 1e7] (+ 2 4 5)))
"Elapsed time: 1200.703487 msecs"
user> (time (dotimes [_ 1e7] (+ 2 (+ 4 5))))
"Elapsed time: 241.716554 msecs"
使用==代替=
使用==比较数字来代?Q提升性能那是相当明显Q?/p>
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
在一D@环种Q如果你想ؓ了提升可L从vector中传出|考虑下标讉K来代替destructing binding。虽然代码看h更清晎ͼ但却非常慢?/p>
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"
优先使用本地变量
如果你需要在循环中查询一个|你或讔R要考虑使用本地变量(通过let定义)来代替全局变量。看下两者的旉ҎQ?/p>
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"
如果你想使用本地变量来提升性能Q可以考虑下面比较土的式的方式来避免全局变量Q?/p>
(let [local-x x]
(defn my-fn [a b c]
...))
使用profiler工具Q?/strong>
JVM有两个profiler工具Q?-Xprof?XrunhprofQ找到程序瓶颈而不是瞎猜?/p>
最后说明:
你已l注意到Q在q些性能提升中,通过调用百万量的执行来提升了几百毫秒的性能。所以,不到万不得已需要提升性能的时候,没必要让你的代码看v来不够清晰?/p>
原文地址: http://gnuvince.wordpress.com/2009/05/11/clojure-performance-tips/
最后补充:可以通过指定~译为staticҎ来提高性能Q?br />