Clojure 2024调研!中分享您的想法。

欢迎!有关如何使用本站的一些更多信息,请查看关于页面。

0
编译器

实际上,Clojure会为每个函数创建一个类文件。结果就是产生过多的.class文件。

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

至少对于AOT编译,将那些.class文件合并起来会好很多?我想知道其背后的技术原因。

3 个答案

+2
by
选定 by
 
最佳答案

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

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

例如

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

这段代码有一个内部函数和外部函数,并编译成两个类。每次调用外部函数时,都会得到一个内部函数类的新的实例,该实例在构造时传入x的值,并将其存储在某个实例字段中。

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

在一般情况下,函数需要能够封闭值(实例字段),并且需要有某种一等表示(实例),因此静态方法不可行。

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

我正在研究一种https://clojure.atlassian.net/browse/CLJ-701的方法,我还没有发布任何补丁(它仍然不起作用),这会导致编译器更积极地替换某些函数定义和调用为静态方法,这样可以完全避免为这些函数生成单独的类,但它的应用范围非常有限,并且只限于在定义函数的地方立即调用且其定义未逃逸的函数。这不会涵盖顶层的定义,因为def将函数的值设置为全局的,有点像是逃逸的定义。

by
感谢信息!
0
by

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

现在,我也记得阅读过它就是这样制作的,然后才逐步发展。我认为也许现在可能还有其他方法可以这样做,可能使用调用动态和方法处理,尽管这超出了我的理解,但如果新的JDK提供这种选项,则需要重写大量内容以进行更改,这并不现实。

我知道这是怎么回事,但我无法确认。

0

我想了解您所看到的'太多类文件'的具体副作用是什么?文件更大、加载时间更长等?

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

多少个类文件算太多?

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