2024年Clojure状态调查!上分享你的想法。

欢迎!请查看关于页面了解关于这个网站的更多信息。

0
编译器

结果是,Clojure为每个函数创建一个类文件。因此,你会得到太多的.class文件。

我的问题是,为什么它要生成这么多.class文件,而不是创建一个按命名空间创建的类文件,并将所有函数都放在这个.class文件中呢?

至少对于AOT编译,如果能合并那些.class文件将是个很大的改进?我想知道背后的技术原因。

3 答案

+2

已选择
 
最佳答案

Clojure的编译器不是一个完整程序的编译器。编译的单位是顶级形式。因此,即使是你说“编译这个文件”,实际上发生的也是该文件中的每个顶级形式都会单独编译,编译器一次只能看到一个顶级形式。

函数是闭包,所以它们不仅仅是代码(就像静态方法一样),它们是代码+闭包的值(比如实例方法,其中闭包的值是实例的字段)。

例如

(fn [x] (fn [y] [x y]))

此代码包含一个内层函数和一个外层函数,并编译成两个类。每当你调用外层函数时,都会得到内层函数类的一个新实例,该实例在构造时接收x的值,并将x存储在某些实例字段中。

使用静态方法实现函数的另一个问题是,函数是一级值,而静态方法不是。你不能将静态方法作为参数传递,等等。反映Iteration方法中存在一些关于静态方法的“一级”表示,例如indy的MethodHandles,但反映是比较慢的,而MethodHandles在clojure最初创建时不可用。在两种情况下,这两个表示都不能用来实现clojure的函数接口IFn,因此需要大量工作来使其生效(很可能是修改clojure调用函数的方式以使用动态调用invoke)。

在一般情况下,函数需要能够封闭值(实例字段)并具有一定的“一级”表示(实例),因此静态方法是不可能的。

但是,clojure编译器确实在某些情况下会做一些优化,可以生成一个静态方法为函数,并在某些情况下将函数调用转换为静态方法调用。但它仍然生成了实例方法版本(它只是调用静态方法),因为编译器不能总是确定正在调用哪个静态函数,所以所有函数都必须支持相同的通用调用约定(作为一个实现IFn的实例)。

我一直在研究一种方法来https://clojure.atlassian.net/browse/CLJ-701(我还没有发布任何补丁,因为它仍然不起作用),这会导致编译器更积极地替换某些函数定义和调用以使用静态方法,这样就完全消除了为这些函数生成单独类的情况,但它非常有限,并且仅限于在定义函数的位置立即调用它们,并且它们的定义没有逃离。这不会覆盖顶级定义,因为def会使得函数的值全局,这有点像逃逸的定义。

by
感谢您的信息!
0
by

我认为问题之一在于,在热加载时,你希望能够一次加载或加载一个函数,但在Java中,你无法再次为现有的类添加或更新方法,但可以在类级别上这么做,因此,通过一个类对应一个函数的方式我们可以一次加载和重新加载一个函数,而不仅仅是整个命名空间。

现在,我也记得读到过,在一定程度上,它是如何制作的,并从这里发展而来的。我认为,现在可能还有其他方式来做这件事,可能使用动态调用和方法处理,尽管这超出了我的理解,但如果新的 JDK 现在有这个选项,那么将需要重写很多代码来改变它,这不太现实。

这就是我所知道的,但我无法确认这一点。

0

你看到“过多类文件”的什么具体副作用?更大的 jar 文件、长的 require 时间等?

你希望优化大量类文件的哪个方面?

有多少个类文件算多?

我正试图将 JVM 字节码转换为 WASM 代码。因此,我需要这些 .class 文件来进行转换。问题是,每个函数都会创建其类,结果是大量文件(1k+,这取决于项目的大小),因此,这使得时间消耗变得很耗时,这就是为什么我试图了解为什么需要为每个函数生成类的背后原因。
...