Clojure 递归函数中的大型绑定是否会影响性能?
Do large bindings inside a Clojure recursive function harm performance?
我正在编写一个函数,它根据一组声明性规则执行一组转换。这些规则是一个编译时评估的集合(没有函数调用或任何东西)。它们将包含数百个元素。
基本布局如下所示:
(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 可以正常工作。
我正在编写一个函数,它根据一组声明性规则执行一组转换。这些规则是一个编译时评估的集合(没有函数调用或任何东西)。它们将包含数百个元素。
基本布局如下所示:
(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 可以正常工作。