以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
的词汇范围在这两种情况下是不同的,我只关心foo
函数是否会定义一遍又一遍多次致电bar
时。
我猜答案是 no ,即没有惩罚,但clojure是如何做到的?
谢谢!
答案 0 :(得分:6)
let
本地方法: foo
只编译一次(当顶级表单时)。这个编译的结果是一个实现clojure.lang.IFn
接口的类;实际的身体存在于该类的invoke(Object)
方法中。在运行时,每次控制到达引入bar
local的foo
中的点时,都会分配该类的新实例;对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
的运行时表示不会带有闭包所需的任何额外字段,而是一个新的实例然而,在foo
的每次调用中都会分配bar
个班级。
defn
方法:有foo
的单个实例,但是调用它涉及通过Var的间接,它本身带有非零成本。除了最具性能敏感性的代码之外,这个成本通常不值得担心,但它就在那里,因此分解本地功能可能不一定是性能上的胜利。像往常一样,如果它值得优化,那么首先值得测量/基准测试。
let
超过lambda
还有丹尼尔提到的最终选项,其中let
绕过defn
,而不是foo
。使用这种方法,有一个(类)bar
的单个实例;它存储在foo
内的字段中;它用于bar
内document.write([1,2,3]);
的所有来电。
答案 1 :(得分:1)
答案是肯定的,这将会受到惩罚,但迈克尔在let local approach
部分指出,这是最小的。
如果你想要你所描述的词法范围,那么将定义foo的let块包围在defn周围吧。这将做你要求的。然而,这是Clojure代码中不常见的模式。