clojure - 以 `let` 形式定义函数的性能问题

标签 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 的词法范围在这两种情况下是不同的,我只是担心 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/

相关文章:

clojure - Clojure实现问题的不同解决方案

clojure - 如何在环应用程序中维护不同用户 session 的状态

clojure java 互操作和接口(interface)

clojure - 如何在 Clojure 中获取调用堆栈?

clojure - 如何将关键字转换为字符串并保留冒号

Clojure 中的映射和记录相等性

concurrency - Clojure 的 ref 与 atom 的并发性

clojure - 在与 Clojure 中运行代码相同的目录中打开文件

clojure - 从 map 中删除零值?

clojure - 使用 clojure 和 quil 进行像素变换的性能