我正在编写一个函数,它根据一组声明性规则执行一组转换。规则是一个编译时评估的集合(没有函数调用或任何东西)。它们将包含数百个元素。
基本布局如下:
(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
会怎么样?
答案 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工作正常。