以"let"形式定义函数的性能问题

Dav*_*zhu 4 clojure

let表单中定义匿名函数并重复调用外部函数是否存在性能损失?像这样:

(defn bar [x y]
  (let [foo (fn [x] (* x x))]
    (+ (foo x) (foo y))))
Run Code Online (Sandbox Code Playgroud)

与定义foo为单独的函数相比,如下所示:

(defn foo [x] (* x x))
(defn bar [x y] (+ (foo x) (foo y)))
Run Code Online (Sandbox Code Playgroud)

我理解foo这两种情况下的词汇范围是不同的,我只关心在多次调用时foo是否会反复定义函数bar.

我猜答案是否定的,即没有惩罚,但是clojure是怎么做到的?

谢谢!

Mic*_*zyk 6

let 本地方法:

foo只编译一次(顶级表单时).这个编译的结果是一个实现clojure.lang.IFn接口的类; 实际的身体生活在invoke(Object)那个阶级的方法中.在运行时,每次控制到达的点bar在哪里foo当地介绍,这个类的一个新实例被分配; 这两个调用foo使用该类的实例.

这是在REPL中证明"单个编译"属性的简单方法:

(defn bar [x y]
  (let [foo (fn [x] (* x x))]
    foo))

(identical? (class (bar 1 2)) (class (bar 1 2)))
;= true
Run Code Online (Sandbox Code Playgroud)

NB.Clojure足够聪明,可以注意到它foo不是一个"实际闭包"(它关闭了参数bar,但它实际上并没有使用它们),所以运行时表示foo不带任何额外的字段,闭包会,但是foo然而,每次调用时都会分配新类的实例bar.

单独的defn方法:

有一个实例foo,但是调用它涉及通过Var的间接,它本身带有非零成本.除了最具性能敏感性的代码之外,这个成本通常不值得担心,但它就在那里,因此分解本地功能可能不一定是性能上的胜利.像往常一样,如果它值得优化,那么首先值得测量/基准测试.

let 过度 lambda

还有丹尼尔提到的最终选项,其中包括let,而不是在里面defn.通过这种方法,有一个(类的)实例foo; 它存储在一个字段里面bar; 它用于所有foo内部调用bar.