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

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

0
编译器

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

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

至少对于AOT编译,将那些.class文件合并在一起并不坏?我想知道背后的技术原因。

3 答案

+2

被选中
 
最佳答案

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

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

例如

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

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

使用静态方法实现fns的另一个问题是,fns是一等公民,而静态方法不是。你不能将静态方法作为参数传递等。有一些静态方法的一等公民表示方式,例如反射的Method和indiy的MethodHandles,但反射被认为速度较慢,而MethodHandles在clojure首次创建时并未可用。在任何情况下,这两种表示方法都不能用来实现clojure的函数接口IFn,因此需要大量的工作才能使它们正常工作(可能需要重新设计clojure调用函数的方式以使用动态调用)。

在一般情况下,函数需要能够封装值(实例字段)并需要有某种一等公民表示(实例),所以静态方法不行。

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

我已经在进行一项关于https://clojure.atlassian.net/browse/CLJ-701的方法研究工作,我还没有发布任何补丁(它仍然不起作用),这会导致编译器更积极地用静态方法替换某些函数定义和调用,并完全消除了为这些函数生成单独类的需求,但它应用范围极其有限,仅限于立即在其定义位置调用的函数,且其定义不脱离。这不会覆盖顶层定义,因为def会让函数值全局化,这就像是定义逃离的范畴。

by
感谢提供信息!
0
by

我认为其中一个问题是,在热加载时,你可能希望能够一次重新加载或加载一个函数,但在Java中,你不能在现有类上添加或更新方法,但可以在类级别上这样操作,所以通过每个函数对应一个类,我们可以一次加载和重新加载一个函数,而不仅仅是加载一个命名空间。

现在,我记得在我阅读过某处,它就是以这种方式实现的,并由此演变而来。我认为也许现在可能有其他方式来做这件事,可能使用动态调用和method handles,尽管这超出了我的理解范围,但如果新的JDK提供了这个选项,因此需要重写很多内容才能改变它,这并不现实。

我知道这些,但不能确认。

0

我想了解一下您所看到的“生成过多类文件”的具体副作用是什么?例如,更大的jar文件、较长的require时间等?

您想要优化大量类文件的哪个方面?

有多少个类文件才算太多?

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