2024 年 Clojure 状态调查!中分享您的想法。

欢迎!请参阅关于页面以获取更多有关如何使用本网站的信息。

0投票
编译器

目前,启用直接链接(DL)方法主要是全有或全无,具有精确的注销选项(`^:redef`)。在我的经验中,这使得在生产环境中使用 DL 变得有些尴尬,因为您仍然希望保留进入应用程序进行调试和调查的能力。提前知道需要重新编译的函数很繁琐且精神负担大。事实上,我最终因为这一点而没有启用 DL。

我想知道,如果有一种更灵活的方式来调整 DL,它是否会更加受欢迎。以下是我对此的一些看法。

目前,是否进行 DL 的决策是在编译调用点时,根据编译调用点时刻的 `*compiler-options*` 的值来决定的。我所设想的一个替代方案是,当在函数定义时间作出 DL 的决策时,该函数的所有未来调用都应遵守这一决策。

让我给你举一个例子。Clojure 本身编译 `clojure.core` 命名空间时启用了 DL。这意味着 `clojure.core` 中的函数会静态链接到其他 `clojure.core` 函数。然而,如果开发人员已禁用 DL,则对 `clojure.core` 函数的调用将通过 Var 解析。我个人无法想象有任何情况会希望这种行为。有人会想在运行代码时重新定义 `clojure.core` 函数,同时允许某些其他代码使用旧版本吗?这是语言设计希望鼓励的吗?

第二点是,覆盖式的 DL 通常只带来非常有限的速度提升,除非用于在紧密循环中调用的特定函数,这些都是由于内联而可以对 JIT 级联优化做出贡献的。这些性能关键函数可以通过开发者使用性能分析器可靠地识别,然后只对那些函数启用 DL 就很合理了。现在,唯一确保函数在没有购买整体 DL 的情况下对 JIT 内联有效的方法是使用半不为人知的 :inline 函数,这毕竟只是一个宏。

从实现方面来看,这可以通过 `*compiler-options*`:`:direct-linking` 的特殊值(有点类似于 `*unchecked-math*` 可以简单的 `true` 或 `:warn-on-boxed`)以及 Vars 上的元数据(可以重用目前无操作的 `^:static`)的组合来实现。

总结:我希望有一种模式,当编译时使用 DL 的函数的所有调用点都自动进行直接链接,并且能够明确标记一个函数始终使用 DL。其他人是否认为这很有用?我很愿意听听您的看法。

1 个答案

+1

在一些特定点上

有人想在保持旧版本仍被某些其他代码使用的情况下,为自己运行的代码重新定义一个 clojure.core 函数吗?

人们确实会这样做,不管这好不好。 https://github.com/generateme/fastmath 是一个例子。

我的第二个观点是,全局的DL通常只能带来相当微小的性能改进

直接链接不仅仅是性能改进(如您所说,除了热循环代码之外通常不显著)。特别是,不需要加载变量进行链接,可以显著减小静态初始化器和常量池大小,从而减小类的大小并减少加载时间。在广泛的范围内进行DL会使这个效果变得显著(clojure.core 特别大,因此这里被放大)。如果我们添加了延迟加载变量,那么加上DL会进一步改进这一点。

尽管如此,我认为DL可能以更加针对性的方式被开放使用,向函数的消费者发出信号可能是有帮助的(类似于 ^:redef - "请直接链接我")。但我认为最好从"我正在尝试做X,但做不到"的角度来看待这个问题,我还没有听到这样的请求。或者,为什么在没有使用DL的地方要使用它?可能是因为缺乏意识,或者可能是因为它与其他功能或工作流程不兼容。

感谢你的回答,Alex!

> 特别是在链接变量的时候,不需要加载变量可以显著减少静态初始化器和常量池大小

公平,我还没有考虑这一点。

> "我正在尝试做X,但做不到"

我将重新说明我的两个用例。

1. 我想确保我的某些函数始终可以被直接链接,以便它们始终可内联(例如,使用类似 `^:static` 元数据的某种东西)。

2. 我想确保所有 `clojure.core` 函数都始终在我的代码或其它代码中被DLed。

目前通过启用DL #1 和 #2 都可以实现,但基于我在问题中提到的理由,我并不希望这么做。当然,我的具体需求可能太过特殊,因此我创建了这个话题,希望有可能收集到更多人们的DL使用场景。如果有更多,这有助于找出如何扩展对DL的控制。
...