【问题标题】:Why is Clojure more hot swappable than other JVM languages?为什么 Clojure 比其他 JVM 语言更可热插拔?
【发布时间】:2018-10-01 06:13:57
【问题描述】:

我们几乎可以立即在运行时重新加载 Clojure 中的任何函数和/或变量。我们甚至可以更改方法签名。对于 Scala 或 Java,我们能做的最多就是使用速度慢、商业化和受限的 JRebel。让 Clojure 具有如此交互性的区别是什么? 在 Slack 中阅读此内容时,我发现了以下 cmets,但我希望了解更多相关信息。还感谢您提供进一步阐明该问题的论文/文章的链接(尽管不是必需的)。

这主要是因为语言设置为可重新加载。 Clojure 对每个函数或顶级变量都有一个 var 间接寻址 你可以改变的定义,所以你可以只重新定义一个函数 同时保持环境的其余部分不变并继续进行

.

跟进 - 当函数名称为 在代码中,但是对于需要另一个长时间运行的函数 作为参数的函数(例如,您将处理函数传递给 http 服务器进程启动)您可以通过以下方式获得 var 间接的好处 手 - 通过传递 #'handler 而不是处理程序,否则你不 重新加载(不重新启动使用该 arg 的进程)

.

一种

直接链接将正在编译的 var 调用替换为直接调用 (已编辑)但是 var 路径仍然存在,并且新代码仍然可以 通过变量调用

【问题讨论】:

  • 看起来您在您发布的报价中找到了问题的答案。大多数 Lisp 允许这种编辑 AFAIK。您可能还想看看 smalltalk。
  • @nha 我觉得我不太了解 cmets。什么是 var 间接?为什么其他语言不直接复制这个想法?有哪些取舍?为什么#'handler 与handler 不同?可以在用户代码中重新定义已编译的方法(例如来自核心库的方法)吗?是否存在安全问题?什么是 var 路径? ...
  • 请阅读 clojure.org/reference/vars swannodette.github.io/2014/12/17/whats-in-a-var blog.cognitect.com/blog/2016/9/15/… 可能还有其他内容,但这些内容应该能让您很好地理解什么是 var。
  • JVM 在字节码级别不再支持不同语言的热交换。但是,如果语言级别更高,它可以在语言级别进行更改,这看起来不像在字节码级别上不受支持的更改。
  • 还因为考虑到 clojure 的语义,您要重新加载的是通常是纯函数。这通常不是真的,例如Java,因为在 Java 中您将重新加载类文件,这可能会破坏您当前重新加载的类实例的结构。

标签: clojure jvm read-eval-print-loop jrebel hotswap


【解决方案1】:

您所问的关键在于 Clojure 如何识别函数并在运行时运行它们。首先,Clojure 函数定义为vars,这是它们的 JVM 根类的 Clojure 名称,Var

Clojure 的运行时维护一个名为 NamespacesConcurrentHashMap。此映射具有 Symbol 键(命名空间名称)和 Namespace 值。每个Namespace 依次有一个AtomicReference'd Clojure map(称为“映射”),它是动态类型的,但本质上具有 Clojure Symbol 键(局部变量名称)和 Var 值。

当您调用 Clojure 函数时,它首先会在 Namespaces 中查找您引用的命名空间,然后在该命名空间的映射中查找特定变量。这使得热加载代码变得微不足道 - 您需要做的就是在给定命名空间的映射上设置一个新的 <Symbol, Var> 对。

更深一层,Clojure 还保持对“框架”(即线程或可能临时重新定义局部范围内的变量的附加绑定)的认识。它们有自己的ThreadLocal 存储,并且将使用在其中之一中找到的变量,而不是当前存储在命名空间映射中的变量。


Clojure 的方法在这里是可行的,因为它不尝试将函数存储为 JVM 函数,而是作为 Java 对象本身保存在可以快速访问的映射中。

Clojure 通过检查它们是否满足函数接口 (IFn) 知道这些对象实际上是可调用的。对象通过具有Invoke 方法来满足IFn。这用于许多非常聪明的目的,并解释了为什么 Clojure 的许多核心数据结构(映射、向量、关键字等)也都可以作为函数调用。

【讨论】:

  • 这不会让调用函数变慢吗?
  • @HappyFace Clojure 函数 与“纯 JVM”等价物相比很慢。但归根结底,“慢”是相对的——使程序在压力下表现不佳的事情往往是由不能很好扩展的算法属性定义的,而不是函数调用花费稍长的事实(这只是线性性能影响)。
  • @HappyFace 关于性能的传统建议是:确保在优化之前先有问题。对于 Clojure,优化可能意味着将某些关键路径转换为纯 Java,但大多数时候,仅在重构代码时就需要进行更高价值的优化。
  • 只是想知道:JIT 编译器消除了多少IFn#invoke 缓慢?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2010-12-28
  • 1970-01-01
  • 1970-01-01
  • 2013-04-06
  • 2010-11-27
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多