目前,启用直接链接(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。其他人是否认为这很有用?我很愿意听听您的看法。