Clojure递归函数中的大型绑定会损害性能吗?

时间:2015-02-07 08:31:41

标签: performance recursion clojure tail-recursion

我正在编写一个函数,它根据一组声明性规则执行一组转换。规则是一个编译时评估的集合(没有函数调用或任何东西)。它们将包含数百个元素。

基本布局如下:

(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足够聪明,只需初始化一次规则?或者将loop置于let绑定中可能有意义吗?

编辑:如果这不是简单的尾递归而是树遍历,并为每个节点递归调用dostuff-with-rules会怎么样?

1 个答案:

答案 0 :(得分:5)

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工作正常。