以 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
时一遍又一遍。
我想答案是否,也就是说,不会有任何惩罚,但是 clojure 是如何做到这一点的呢?
谢谢!
最佳答案
让
本地方法:
foo
仅编译一次(当顶级表单编译时)。编译的结果是一个实现 clojure.lang.IFn 接口(interface)的类;实际的主体位于该类的 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
注意。 Clojure 足够聪明,注意到 foo
不是一个“实际的闭包”(它关闭了 bar
的参数,但实际上并没有使用它们),因此foo
的运行时表示不携带闭包所携带的任何额外字段,但每次调用 bar< 时都会分配一个
.foo
类的新实例。/
单独的defn
方法:
有一个 foo
实例,但是调用它涉及通过 Var 的间接寻址,它本身具有非零成本。除了对性能最敏感的代码之外,这种成本通常不值得担心,但它确实存在,因此分解本地函数可能不一定会带来性能优势。像往常一样,如果值得优化,就值得首先进行测量/基准测试。
让
超过lambda
还有 Daniel 提到的最后一个选项,其中 let
围绕 defn
运行,而不是在 defn
内部。通过这种方法,有一个foo
(的类)实例;它存储在 bar
内的字段中;它用于对 bar
内的 foo
的所有调用。
关于clojure - 以 `let` 形式定义函数的性能问题,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/31827017/