`let` 在 Clojure 中是如何实现的,它的开销是多少?
Posted
技术标签:
【中文标题】`let` 在 Clojure 中是如何实现的,它的开销是多少?【英文标题】:How is `let` implemented in Clojure and what is its overhead? 【发布时间】:2012-05-23 14:19:32 【问题描述】:我可以看到两种实现let
绑定的方法。首先,从SICP 得知,let
可以实现为 lambda 函数。这既方便又简单,但考虑到每个 lambda (fn
) 在 JVM 中被翻译成单独的类以及let
在普通程序中使用的次数,这似乎非常非常昂贵。
其次,let
绑定可以直接翻译成本地 Java 变量。这几乎没有开销,但是将绑定存储在堆栈上会破坏语言语义:在这种情况下,创建闭包是不可能的 - 保存的值将在堆栈展开后立即被销毁。
那么 Clojure 中使用的实际实现是什么?指向 Clojure 源代码中的相应行表示赞赏。
【问题讨论】:
【参考方案1】:let
-bound 变量作为 final 本地值存储在堆栈中。
由于它们是最终的,如果需要,它们可以绑定到闭包中(这类似于在 Java 的匿名内部类中使用最终局部变量的方式)。在底层,JVM 将值复制到代表闭包的对象中(它被存储为最终字段)。因此,即使在堆栈帧消失后,闭包仍然有效。
总体而言,let-bound 变量的开销极低,从性能角度来看,您应该毫不犹豫地使用它们。在 JVM 上可能再好不过了。
【讨论】:
所以我知道闭包对象仅在需要时创建(并将最终变量复制到其中),并且对于简单的情况,vars 仅保留在堆栈上? 是的,这就是它的工作原理。如果你有兴趣,你可以去挖掘一下:github.com/clojure/clojure/blob/master/src/jvm/clojure/lang/…(虽然要弄清楚 Clojure 的编译器在做什么并不是特别容易,但有很多“魔法”) @ffriend Java 具有块范围的变量(与方法范围相反)。我没有看过 Clojure 编译器做了什么,但似乎 let 可以简单地通过包含最终变量的代码块来实现。我不确定你为什么认为它会通过闭包来实现。 @AlexandreJasmin:块范围的变量不是存储在堆栈上吗?在这种情况下,问题仍然存在:堆栈展开后,这些变量将不可用。因此,为了稍后获取它们的值,它们将被复制到内存中的某个位置,例如在闭包对象中(闭包只是一些 Lisp 实现的方式,在 Java 中它可能是任何其他类型的对象)。我猜想仅在需要时才创建用于存储这些变量的其他对象,但不确定。 mikera 的回答更清楚地说明了这一点。 @ffriend Java 中的匿名内部类关闭最终变量和最终方法参数。我想我的观点是 lambda(匿名类)实例将维护封闭变量的副本。 let 特别不应该包含任何东西,但范围内的最终变量初始化。然后我又没有看编译器实际上在做什么......【参考方案2】:局部变量是指针,分配在栈上,指向堆上的值/对象。指针超出范围,但只要闭包保留指向它的指针,对象就会保持活动状态。
【讨论】:
以上是关于`let` 在 Clojure 中是如何实现的,它的开销是多少?的主要内容,如果未能解决你的问题,请参考以下文章