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

欢迎!请参阅关于页面了解有关如何操作的更多信息。

0

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

我的问题是,为什么它会生成那么多的.class文件,而不是创建一个包含所有函数的类文件?

至少对于AOT编译,将那些.class文件合并在一起会很好吗?我想知道背后技术的理由。

3 答案

+2

被选中
 
最佳答案

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

函数是闭包,因此它们不仅仅是代码(像静态方法),它们是代码+封闭的值(像实例方法,其中封闭的值是实例的字段)。

例如:

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

此代码包含一个内部函数和一个外部函数,编译成两个类。每次调用外部函数时,都会为内部函数的类生成一个新的实例,在构造时传入x的值,并将x存储在某个实例字段中。

使用静态方法实现函数的另一个问题是函数是一等值,而静态方法不是。你不能将静态方法作为参数传递等。有一些一等静态方法表示,比如反射的Method和indy的MethodHandles,但反射被认为较慢,而且当Clojure首次创建时,MethodHandles尚未提供。无论如何,这两种表示都不能实现Clojure的函数接口IFn,因此需要大量工作才能使它们正常工作(可能需要重新设计Clojure调用函数的方式以使用动态调用)。

在一般情况下,函数需要能够封装值(实例字段),并需要某种一等表示(实例),因此静态方法是不合适的。

但Clojure编译器确实进行了一些优化,可以在某些情况下为函数生成静态方法,并将函数调用转换为静态方法调用。但仍然生成实例方法版本(该版本仅调用静态方法),因为编译器并不能总是确定正在调用的函数,所以必须使所有函数都支持相同的一般调用约定(成为实现IFn的实例)。

我一直在研究一个方法,用于修复https://clojure.atlassian.net/browse/CLJ-701问题,我还没有发布补丁(它仍然不可用),这使得编译器更积极地用静态方法替换某些函数定义和调用,并完全消除了为这些函数生成单独类的问题,但它适用范围非常有限,并且仅限于在定义的地方立即调用的函数,且函数定义不得逃离。这不会覆盖顶层def,因为def会使函数值全球有效,有点像逃逸的定义。

感谢您提供的信息!
0

我认为一个问题是,在热加载时,你希望能够一次重新加载或加载一个函数,但在Java中,不能在现有类上添加或更新方法,但可以在类级别更新,因此通过为每个函数创建一个类,我们可以在一次加载和重新加载中加载一个函数,而不是一次只加载一个命名空间。

现在,我还记得我读到过,在某种程度上,就是这样制作的,然后它从那里发展而来。我认为现在可能还有其他方法来做这件事,可能是使用动态调用和方法句柄,尽管这超出了我的理解范围,但如果新的 JDK 现在提供了这个选项,那么大量的代码需要重写以进行更改,这将不切实际。

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

0

你对“产生过多类文件”的实际副作用是什么感到好奇?更大的 jar 文件、较长的加载时间等?

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

多少个类文件才算多?

我正在尝试从 JVM 字节码生成 WASM 代码。因此,我需要 .class 文件进行转换。问题是,每个函数都会创建自己的类(们),结果是有大量文件(1k+,根据项目大小而变化),因此这变得耗时,这就是为什么我试图了解为什么每个函数都要生成类的理由。
...