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
over
lambda

还有Daniel提到的最后一个选项,
let
defn
中循环,而不是在
defn中循环。使用这种方法,就有一个(类)foo
foo
的实例;它存储在
条内的字段中
;它用于对
foo
内部
bar
的所有调用,非常好!完全回答了问题并提供了更多的阅读资料。所谓惩罚,我最初的意思是将函数
foo
编译成
IFn
类。因为这只需要执行一次,所以在我的场景中,实例化新对象的成本是可以接受的。