Clojure递归函数内部的大型绑定是否会损害性能?

问题描述:

我在写一个函数,它根据一组声明规则进行一组转换。规则是一个编译时评估的集合(无函数调用或任何其他)。它们将包含数百个元素。Clojure递归函数内部的大型绑定是否会损害性能?

基本布局是这样的:

(defn dostuff-with-rules [stuff] 
    (let [rules [["foo"] ["bar"] ["baz"] ...] 
     transformed (reduce apply-rule stuff rules)] 
    (if (not= stuff transformed) 
     (recur transformed) 
     transformed)))) 

我担心初始化一个大的数据集的每一个函数调用会影响性能,这是更好的移动rules外的功能范围。

这是否有意义,还是Clojure足够聪明,只是初始化规则一次?或者,在let绑定内部放置loop是否有意义?

编辑:如果这不是一个简单的尾递归,而是树遍历,而递归调用每个节点的dostuff-with-rules

Clojure(或者说编译器和JVM的组合)确实足够聪明,在循环和重复使用时不会重复分配常量,因此在该帐户上遇到问题的风险很小。如果你有一个昂贵的函数来初始化规则并把它放在loop/fn/recur中,那么它确实会成为一个问题,尽管它很容易修复。

这里是一个向量每次重新计算的例子:

user> (time (loop [a 1] 
       (if (< a 4) 
       (let [big (vec (range 10e6))] 
        (do (println (rand-nth big)) 
         (recur (inc a))))))) 
9528975 
717854 
729682 
"Elapsed time: 3753.978349 msecs" 
nil 

和这个常数被引用:

user> (def big (vec (range 10e6))) 
#'user/big 
user> (time (loop [a 1] 
       (if (< a 4) 
       (do (println (rand-nth big)) 
        (recur (inc a)))))) 
4002962 
7528467 
2596236 
"Elapsed time: 0.685522 msecs" 
nil 

所以,如果你把你的规则,在恒定的,例如从配置文件加载它们,那么你将获得快速的性能和一个有意义的方式来管理它们。

如果您尝试使用太大的文字值(在这种情况下,我产生它在一个宏)它失败

user> (defmacro make-big-vec [] (vec (range 10000))) 
#'user/make-big-vec 
user> (time (loop [a 1] 
       (if (< a 4) 
       (let [big (make-big-vec)] 
        (do (println (rand-nth big)) 
         (recur (inc a))))))) 
CompilerException java.lang.RuntimeException: Method code too large!, compiling:(/tmp/form-init1716519094506420012.clj:1:7) 

虽然1000工作正常。