Clojure 以“let”形式定义函数的性能问题
在Clojure 以“let”形式定义函数的性能问题,clojure,Clojure,在let表单中定义匿名函数并重复调用外部函数是否会造成性能损失?像这样: (defn bar [x y] (let [foo (fn [x] (* x x))] (+ (foo x) (foo y)))) 与将foo定义为单独的函数相比,如下所示: (defn foo [x] (* x x)) (defn bar [x y] (+ (foo x) (foo y))) 我知道foo的词法范围在这两种情况下是不同的,我只是担心在多次调用bar时是否会反复定义foo函数 我猜答案是否定
let
表单中定义匿名函数并重复调用外部函数是否会造成性能损失?像这样:
(defn bar [x y]
(let [foo (fn [x] (* x x))]
(+ (foo x) (foo y))))
与将foo
定义为单独的函数相比,如下所示:
(defn foo [x] (* x x))
(defn bar [x y] (+ (foo x) (foo y)))
我知道foo
的词法范围在这两种情况下是不同的,我只是担心在多次调用bar
时是否会反复定义foo
函数
我猜答案是否定的,也就是说,没有处罚,但是clojure是怎么做到的
谢谢大家! 答案是肯定的,这将是一个惩罚,但正如Michael在
让本地方法
一节中指出的,这是最低限度的
如果您想要如您所述的词法范围,那么将定义foo的let块环绕在defn for bar上。这会满足你的要求。然而,这是Clojure代码中不常见的模式 让
本地方法:
foo
仅编译一次(当顶级表单为空时)。编译的结果是一个类实现了clojure.lang.IFn
接口;实际的主体存在于该类的invoke(Object)
方法中。在运行时,每次控件到达bar
中引入foo
local的点时,都会分配该类的新实例;对foo
的两个调用使用该类的实例
以下是在REPL上证明“单一编译”属性的简单方法:
(defn bar [x y]
(let [foo (fn [x] (* x x))]
foo))
(identical? (class (bar 1 2)) (class (bar 1 2)))
;= true
注意。Clojure非常聪明,注意到foo
不是一个“实际的闭包”(它关闭bar
的参数,但实际上并不使用这些参数),因此foo
的运行时表示形式不包含闭包可能包含的任何额外字段,但是,foo
类的一个新实例仍然会在每次调用bar
时分配
分离defn
进近:
有一个foo
的实例,但是调用它需要通过Var进行间接寻址,而Var本身具有非零成本。除了对性能最敏感的代码外,这种成本通常不值得担心,但它确实存在,因此分解出一个局部函数并不一定是一种性能上的胜利。和往常一样,如果它值得优化,那么首先值得测量/基准测试
let
overlambda
还有Daniel提到的最后一个选项,
let
在defn
中循环,而不是在defn中循环。使用这种方法,就有一个(类)foofoo
的实例;它存储在条内的字段中
;它用于对foo
内部bar
的所有调用,非常好!完全回答了问题并提供了更多的阅读资料。所谓惩罚,我最初的意思是将函数foo
编译成IFn
类。因为这只需要执行一次,所以在我的场景中,实例化新对象的成本是可以接受的。