问题描述
我希望以编程方式(而不是通过IDE)编译一些小类,例如:
public class Sum {
public int sum(int[] nums) {
int total = 0;
for (int i = 0; i < nums.length; i++) {
total += nums[i];
}
return total;
}
}
在macOS(2.3 GHz Intel Core i5)上运行javac Sum.java
花费429ms
,但是在具有3Vcpu和6G RAM的Kubernetes容器(t3.xlarge)中,花费640ms
。是什么原因导致这种差异?
我尝试了不同的Java版本,并尝试使用javax.tools.JavaCompiler
,但是文件很少,最多需要2秒的时间来编译3个像这样的小文件。
使这些小文件最快地进行编译的最佳硬件/软件配置是什么?
解决方法
我凭经验得到的数字是5ms /文件...
如果要编译的Java文件很多,那么也许您对此很在意。但是,如果是这样的话,那么您所谈论的计时数字就不切实际。我编写了此程序以编译您的测试程序1000次(我创建了1000个Java文件,定义了Sum0至Sum999类。
import javax.tools.JavaCompiler;
import javax.tools.ToolProvider;
public class T {
public static void main(String[] args) {
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
int N = 1000;
String[] files = new String[N];
for (int i = 0 ; i < N ; i++) {
files[i] = String.format("stage/Sum%d.java",i);
}
int result = compiler.run(null,null,files);
System.out.println("Compile result code = " + result);
}
}
我运行了,这是该运行的时间图:
> ls stage/*.class | wc -l
ls: stage/*.class: No such file or directory
0
> time java T
Compile result code = 0
real 0m2.511s
user 0m4.737s
sys 0m0.549s
> ls stage/*.class | wc -l
1000
因此,总运行时间为4.737秒/ 1000 = .005秒= 5毫秒/文件。我所能想到的就是您一次只编译一个文件,在这种情况下,总是需要花费一定的启动/拆卸成本,而谁在乎呢?
最重要的是,您可能仅需几秒钟就可以编译任意数量的Java文件,因此不必为此担心。
这是在几代MacBook Pro上运行的。
,Wahaaaaaay在遥远的过去,我们有了“增量编译器”。这些将连续运行。您可以在一天开始之初启动它们,或者什么都可以,然后让它们永远运行。然后,他们要么等待任何键盘输入(按Enter),要么设置一些文件监视(当在给定目录中修改任何文件时触发的钩子),并在需要时重新编译。这样可以节省VM预热和VM引导加载。
我认为随着构建工具(尤其是Maven和Gradle)的流行,它们大部分都消失了。该模型无法与构建系统很好地结合;构建系统本身应该是增量单元(应该运行一次并保持加载状态),因此还必须内部化编译器,以避免每次需要编译时都要初始化和预热VM。
鉴于您要编译一个简单的单个小文件,VM初始化和编译器预热很可能是640毫秒中的99%。增量编译器正是您所需要的。
增量编译器仍然存在,但是我所知道的唯一的现代编译器是eclipse:如果将文件保存在eclipse中,则eclipse几乎会立即使用其自己的内置编译器对其进行编译。那是..显然不是您想要的。
如果仍然存在一些增量文件并且维护得当,您可以在网上搜索。您也许应该也可以这样做:拥有一个Java应用程序,该应用程序将执行以下操作:侦听系统输入中的任何完整行(例如,使用Scanner和.useDelimiter("\r?\n"); .next()
,将这些行视为文件或目录,将进行扫描那些用于任何新文件或已更改文件的文件,请对所有已更改的内容调用javax.tools.JavaCompiler
,然后将哈希映射文件更新为时间戳,然后返回睡眠状态以等待更多System.in流量,使用该映射来决定缓存或者忽略地图并保留旧样式:找到类文件,将其时间戳与源文件的时间戳进行比较,如果类文件的标记是较新的,则无需重新编译它。 java可以正常工作(1个源文件可以产生多个类文件,并且无法从类文件名确定是哪个源文件产生了它,尽管您可以添加足够的调试信息并打开它,但是可以,但是类文件是复杂的格式,所以这样做是不平凡的)-所以我会做hashmap,即使compi以前的用户都选择了“比较类文件和源文件的最后修改”。
或者,它可以打开一个TCP / IP端口。
请注意,eclipse也以“语言服务器”形式出现,这正是VSCode的工作方式:在VSCode中编辑Java代码时,VS启动无头蚀,并使用它来完成有关Java的所有“智能”操作:重构脚本,实时编译,错误,警告和常规整理服务,导航服务(例如“打开类型”,“查找调用者”),调试器等。编译是eclipse-as-a-language-server提供的众多服务的一部分,并且ecj的启动速度比javac引导的速度高4至10倍(VM初始化和预热后的实际编译速度可能只是Java的一小部分) 640毫秒,因此无关紧要,但是我不知道您到底需要多快。