在let表单中定义匿名函数并重复调用外部函数是否有任何性能损失?像这样: (defn bar [x y] (let [foo (fn [x] (* x x))] (+ (foo x) (foo y)))) 与将foo定义为单独的函数相比,如下所示: (defn foo [x] (
(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的词法范围在这两种情况下是不同的,我只关心foo函数是否会在多次调用bar时反复定义.
我猜答案是否定的,即没有惩罚,但是clojure是怎么做到的?
谢谢!
让本地方法:foo只编译一次(顶层表单时).这个编译的结果是一个实现clojure.lang.IFn接口的类;实际的body存在于该类的invoke(Object)方法中.在运行时,每次控制到达引入foo本地的bar中的点时,都会分配该类的新实例;对foo的两次调用使用该类的实例.
这是在REPL中证明“单个编译”属性的简单方法:
(defn bar [x y] (let [foo (fn [x] (* x x))] foo)) (identical? (class (bar 1 2)) (class (bar 1 2))) ;= true
NB. Clojure足够聪明,可以注意到foo不是“实际闭包”(它关闭了bar的参数,但它实际上并没有使用它们),因此foo的运行时表示不带任何额外的字段,闭包会但是,每次调用bar都会分配一个foo类的新实例.
单独的定义方法:
有一个foo实例,但是调用它涉及到Var的间接,它本身带有非零成本.除了最具性能敏感性的代码之外,这个成本通常不值得担心,但它就在那里,因此将本地功能分解出去可能不一定是性能上的胜利.像往常一样,如果它值得优化,那么首先值得测量/基准测试.
放过lambda
还有丹尼尔提到的最后一个选项,其中释放,而不是内部,定义.使用这种方法,有一个(类)foo的单个实例;它存储在栏内的字段中;它用于所有对foo内部的调用.