将刺猬与刺猬杂交:OpenJDK-11 + GraalVM

哈Ha! 鉴于不是有关Oracle有关Java许可政策的最新消息,从Oracle版本转移到OpenJDK的问题变得越来越尖锐。 长期以来,OracleLabs的Odanko一直在做一个很酷的事情,叫做GraalVM ,这是一个用Java编写的很酷的JIT编译器,也是一个运行时,用于以JavaScript,Ruby,Python,C,C ++,Scala,Kotlin, R,克洛瑞尔。 令人印象深刻,对不对? 但我不想告诉您多语言环境的凉爽。 我们将讨论将最新的grail程序集集成到OpenJDK 11生态系统中的困难,以及一些有关性能的内容,相当多...

首先是这个词


我与graalvm相识的故事始于2017年的小丑克里斯·西顿Chris Seaton )在会上详细介绍了编译器的内部原理,并展示了使用圣杯交付中的本机映像作为示例进行AOT编译的妙处(这是一个将Java代码编译成本机二进制文件的笑话)。

这份报告之后,我接受了很长时间的培训,以编译我的宠物项目的本机二进制文件,拐杖和耙子以便在所有地方获得反映(可能还不行!),最后偶然发现了IO的未解决问题(有些东西那里没有和动物园管理员一起起飞,现在我不记得了。) 在本机图像上吐出:-(

2018年,奥列格·谢拉耶夫(Oleg Shelaev)关于AOT的超级详细报告中,同样的小丑和相同的graalvm。

在报告和演示中,一切看起来都很酷,而且光盘上还有一个宠物项目……该是时候揭露终端机,挥舞新的圣杯候选者并投入战斗了! 我们会碰一下。

世界你好


接触并踢一个小小的JIT(在撰写本文时是ce-1.0.0-rc14版本),我们将使用一段代码来测试站点https://graalvm.org的性能,这是我们的第一个示例。

没有任何构建系统的java项目(甚至是Hello World )正在做什么? 没错,只有Javac会学习做饭的人。 我们不会学习烹饪Javak,让Maven引导Javak。

因此,满足pom.xml:

pom.xml
<?xml version="1.0"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.mycompany.app</groupId> <artifactId>my-app</artifactId> <!--<packaging>jar</packaging>--> <packaging>jar</packaging> <version>1.0-SNAPSHOT</version> <name>my-app</name> <url>http://maven.apache.org</url> <properties> <java.version>11</java.version> <graalvm.version>1.0.0-rc14</graalvm.version> </properties> <profiles> <profile> <id>jdk11</id> <activation> <jdk>11</jdk> </activation> <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-dependency-plugin</artifactId> <version>2.10</version> <executions> <execution> <id>copy</id> <phase>process-test-classes</phase> <goals> <goal>copy-dependencies</goal> </goals> <configuration> <outputDirectory>${project.build.directory}/lib</outputDirectory> </configuration> </execution> </executions> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-jar-plugin</artifactId> <configuration> <archive> <manifest> <mainClass>com.mycompany.app.App</mainClass> </manifest> </archive> </configuration> </plugin> </plugins> </build> </profile> </profiles> <build> <plugins> <plugin> <artifactId>maven-compiler-plugin</artifactId> <version>3.8.0</version> <configuration> <release>${java.version}</release> </configuration> </plugin> </plugins> </build> <dependencies> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> <scope>test</scope> </dependency> <dependency> <groupId>org.graalvm.compiler</groupId> <artifactId>compiler</artifactId> <version>${graalvm.version}</version> </dependency> <dependency> <groupId>org.graalvm.truffle</groupId> <artifactId>truffle-api</artifactId> <version>${graalvm.version}</version> </dependency> <dependency> <groupId>org.graalvm.sdk</groupId> <artifactId>graal-sdk</artifactId> <version>${graalvm.version}</version> </dependency> <dependency> <groupId>org.graalvm.js</groupId> <artifactId>js</artifactId> <version>${graalvm.version}</version> </dependency> <dependency> <groupId>org.graalvm.js</groupId> <artifactId>js-scriptengine</artifactId> <version>${graalvm.version}</version> </dependency> </dependencies> </project> 


项目文件结构如下所示:



类代码com.mycompany.app.App (来自graalvm.org的复制粘贴示例):

App.java
  package com.mycompany.app; public class App { static final int ITERATIONS = Math.max(Integer.getInteger("iterations", 1), 1); public static void main(String[] args) { String sentence = String.join(" ", args); for (int iter = 0; iter < ITERATIONS; iter++) { if (ITERATIONS != 1) System.out.println("-- iteration " + (iter + 1) + " --"); long total = 0, start = System.currentTimeMillis(), last = start; for (int i = 1; i < 10_000_000; i++) { total += sentence.chars().filter(Character::isUpperCase).count(); if (i % 1_000_000 == 0) { long now = System.currentTimeMillis(); System.out.printf("%d (%d ms)%n", i / 1_000_000, now - last); last = now; } } System.out.printf("total: %d (%d ms)%n", total, System.currentTimeMillis() - start); } } } 


代码模块info.java

module-info.java
  module com.mycompany.app {} 


嗯,是空的。但是它是空的,因为我想向您展示自定义Java(稍后再介绍自定义Java)将如何使用我们的应用程序需要的未声明模块发誓。

第一煎饼...


不块状。 让我们组装我们的项目并启动它。 这样说:

 mvn clean package 


我们推出:

 $JAVA_HOME/bin/java -XX:+UnlockExperimentalVMOptions -XX:+UseJVMCICompiler -Diterations=10 -jar target/my-app-1.0-SNAPSHOT.jar In 2017 I would like to run ALL languages in one VM. 

有两个要点: JVMCI是从版本9开始出现在Java中的实验性事物,因此我们需要:

  • 启用实验性虚拟机选项-
      -XX:+ UnlockEperimentalVMOptions 
  • 启用非常试验性的虚拟机(从9开始,openjdk中使用的不是最新版本,但仍是最新版本)--XX:+ UseJVMCICompiler

启动结果
-迭代1-1(1466毫秒)
2(461毫秒)
3(463毫秒)
4(138毫秒)
5(151毫秒)
6(159毫秒)
7(266毫秒)
8(128毫秒)
9(144毫秒)
总计:69999993(3481毫秒)
-迭代2-1(233毫秒)
2(169毫秒)
3(121毫秒)
4(205毫秒)
5(170毫秒)
6(152毫秒)
7(227毫秒)
8(158毫秒)
9(108毫秒)
总计:69999993(1644毫秒)
-迭代3-1(98毫秒)
2(102毫秒)
3(98毫秒)
4(102毫秒)
5(95毫秒)
6(96毫秒)
7(101毫秒)
8(95毫秒)
9(97毫秒)
总计:69999993(990毫秒)
-迭代4-1(109毫秒)
2(114毫秒)
3(97毫秒)
4(98毫秒)
5(100毫秒)
6(103毫秒)
7(125毫秒)
8(108毫秒)
9(100毫秒)
总计:69999993(1056毫秒)
-迭代5-1(98毫秒)
2(100毫秒)
3(105毫秒)
4(97毫秒)
5(95毫秒)
6(99毫秒)
7(95毫秒)
8(123毫秒)
9(98毫秒)
总计:69999993(1010毫秒)
-迭代6-1(99毫秒)
2(95毫秒)
3(102毫秒)
4(99毫秒)
5(96毫秒)
6(100毫秒)
7(99毫秒)
8(99毫秒)
9(104毫秒)
总计:69999993(993毫秒)
-迭代7-1(100毫秒)
2(104毫秒)
3(95毫秒)
4(96毫秒)
5(97毫秒)
6(95毫秒)
7(94毫秒)
8(108毫秒)
9(108毫秒)
总计:69999993(1000毫秒)
-迭代8-1(100毫秒)
2(106毫秒)
3(99毫秒)
4(95毫秒)
5(97毫秒)
6(97毫秒)
7(101毫秒)
8(99毫秒)
9(101毫秒)
总计:69999993(1012毫秒)
-迭代9-1(105 ms)
2(97毫秒)
3(98毫秒)
4(96毫秒)
5(99毫秒)
6(96毫秒)
7(94毫秒)
8(98毫秒)
9(105毫秒)
总计:69999993(993毫秒)
-迭代10-1(107 ms)
2(98毫秒)
3(99毫秒)
4(100毫秒)
5(97毫秒)
6(101毫秒)
7(98毫秒)
8(103毫秒)
9(105毫秒)
总计:69999993(1006毫秒)

我们在这里看到什么? 而且我们看到第一次迭代是最长的(3.5秒),此JIT正在预热。 然后,一切都会或多或少变得平滑(在一秒钟之内)。

但是,如果我们给Java一个新版本的圣杯呢? 快做决定:

 java -XX:+UnlockExperimentalVMOptions -XX:+UseJVMCICompiler -Diterations=10 --module-path=target/lib --upgrade-module-path=target/lib/compiler-1.0.0-rc14.jar -jar target/my-app-1.0-SNAPSHOT.jar In 2017 I would like to run ALL languages in one VM. 

新的圣杯发射结果
-迭代1-1(1789 ms)
2(547毫秒)
3(313毫秒)
4(87毫秒)
5(88毫秒)
6(87毫秒)
7(83毫秒)
8(92毫秒)
9(87毫秒)
总计:69999993(3259毫秒)
-迭代2-1(241毫秒)
2(161毫秒)
3(152毫秒)
4(195毫秒)
5(136毫秒)
6(129毫秒)
7(154毫秒)
8(176毫秒)
9(109毫秒)
总计:69999993(1553毫秒)
-迭代3-1(109毫秒)
2(103毫秒)
3(113毫秒)
4(172毫秒)
5(141毫秒)
6(148毫秒)
7(111毫秒)
8(102毫秒)
9(101毫秒)
总计:69999993(1211毫秒)
-迭代4-1(96毫秒)
2(96毫秒)
3(104毫秒)
4(98毫秒)
5(96毫秒)
6(97毫秒)
7(98毫秒)
8(96毫秒)
9(95毫秒)
总计:69999993(972毫秒)
-迭代5-1(97毫秒)
2(93毫秒)
3(99毫秒)
4(97毫秒)
5(97毫秒)
6(97毫秒)
7(95毫秒)
8(98毫秒)
9(94毫秒)
总计:69999993(965毫秒)
-迭代6-1(96毫秒)
2(95毫秒)
3(96毫秒)
4(99毫秒)
5(102毫秒)
6(94毫秒)
7(99毫秒)
8(115毫秒)
9(109毫秒)
总计:69999993(1001毫秒)
-迭代7-1(98毫秒)
2(96毫秒)
3(99毫秒)
4(98毫秒)
5(118毫秒)
6(98毫秒)
7(95毫秒)
8(99毫秒)
9(116毫秒)
总计:69999993(1017毫秒)
-迭代8-1(95 ms)
2(99毫秒)
3(99毫秒)
4(106毫秒)
5(101毫秒)
6(101毫秒)
7(93毫秒)
8(97毫秒)
9(108毫秒)
总计:69999993(993毫秒)
-迭代9-1(102 ms)
2(95毫秒)
3(97毫秒)
4(125毫秒)
5(94毫秒)
6(101毫秒)
7(100毫秒)
8(95毫秒)
9(96毫秒)
总计:69999993(1008毫秒)
-迭代10-1(97 ms)
2(97毫秒)
3(99毫秒)
4(112毫秒)
5(102毫秒)
6(96毫秒)
7(96毫秒)
8(98毫秒)
9(96毫秒)
总计:69999993(988毫秒)

如我们所见,结果并没有太大不同。

我忘了 好吧,如果没有新型的JIT编译器,我们不会尝试运行相同的东西。 我们将做:

 java -Diterations=10 -jar target/my-app-1.0-SNAPSHOT.jar In 2017 I would like to run ALL languages in one VM. 

没有新的JIT编译器的结果
-迭代1-1(372毫秒)
2(271毫秒)
3(337毫秒)
4(391毫秒)
5(328毫秒)
6(273毫秒)
7(239毫秒)
8(271毫秒)
9(250毫秒)
总计:69999993(2978毫秒)
-迭代2-1(242毫秒)
2(253毫秒)
3(253毫秒)
4(240毫秒)
5(245毫秒)
6(275毫秒)
7(273毫秒)
8(263毫秒)
9(234毫秒)
总计:69999993(2533毫秒)
-迭代3-1(237 ms)
2(235毫秒)
3(234毫秒)
4(246毫秒)
5(242毫秒)
6(238毫秒)
7(244毫秒)
8(243毫秒)
9(253毫秒)
总计:69999993(2414毫秒)
-迭代4-1(244毫秒)
2(249毫秒)
3(245毫秒)
4(243毫秒)
5(232毫秒)
6(256毫秒)
7(321毫秒)
8(303毫秒)
9(249毫秒)
总计:69999993(2599毫秒)
-迭代5-1(246毫秒)
2(242毫秒)
3(248毫秒)
4(256毫秒)
5(280毫秒)
6(233毫秒)
7(235毫秒)
8(266毫秒)
9(246毫秒)
总计:69999993(2511毫秒)
-迭代6-1(292毫秒)
2(368毫秒)
3(339毫秒)
4(251毫秒)
5(267毫秒)
6(259毫秒)
7(289毫秒)
8(262毫秒)
9(357毫秒)
总计:69999993(3058毫秒)
-迭代7-1(284毫秒)
2(258毫秒)
3(248毫秒)
4(247毫秒)
5(266毫秒)
6(247毫秒)
7(242毫秒)
8(314毫秒)
9(265毫秒)
总计:69999993(2656毫秒)
-迭代8-1(239 ms)
2(238毫秒)
3(257毫秒)
4(282毫秒)
5(244毫秒)
6(261毫秒)
7(253毫秒)
8(295毫秒)
9(256毫秒)
总计:69999993(2575毫秒)
-迭代9-1(273 ms)
2(243毫秒)
3(239毫秒)
4(240毫秒)
5(250毫秒)
6(285毫秒)
7(266毫秒)
8(285毫秒)
9(264毫秒)
总计:69999993(2617毫秒)
-迭代10-1(245毫秒)
2(264毫秒)
3(258毫秒)
4(253毫秒)
5(239毫秒)
6(260毫秒)
7(251毫秒)
8(250毫秒)
9(256毫秒)
总计:69999993(2538毫秒)

结果是不同而体面的。

C2不对热门代码提供任何优化-每次迭代都在同一时间进行。

Graal能够优化热门代码,并且从长远来看可以提高性能。

那又怎样


当我们想为项目添加新功能,新工具,新虚拟机,新JIT时,这可能是您需要问自己和其他人(团队成员)的主要问题。

正如上面所写,我的故事始于2017年的Joker,然后人们进行了很多尝试以掌握AOT,现在我尝到了Java的JIT for Java的乐趣。

磁盘上的一个宠物项目是一种业务流程引擎,其中流程由应用程序开发人员在浏览器UI中绘制,并且它们具有用JS编写脚本的能力,该脚本将在JVM上运行。

他们承诺在Java的未来版本中删除nashorn,GraalVM逐渐接近该版本。

好吧,问题的答案是这样的:

  1. 我们希望运行时运行JS(不仅是)
  2. 要速度准时
  3. 我们希望启动pet-project应用程序像以前一样在8-ke上出现(没有任何
      --module-path 
      --upgrade-module-path 
    但有一个新的圣杯组件)

捷联


上面问题的答案列表中的前两点很清楚,让我们处理后者。

事实上,developers-admins-devops很懒惰,不喜欢做额外的工作(我也很喜欢),他们尝试使所有内容自动化并将其打包到一个可以作为简单binar运行的现成捆绑中。 好了,有一个问题,让我们解决。

我们从Java 9+的世界中获得了一个相对较新的工具,它的名称为jlink 。 我们正在尝试将应用程序与所有必需的库打包在一起:

 jlink --module-path target/classes:target/lib:$JAVA_HOME/jmods --add-modules com.mycompany.app --launcher app=com.mycompany.app/com.mycompany.app.App --compress 2 --no-header-files --no-man-pages --strip-debug --output test 

我们描述了多少种各种主要参数:
  •   --module-path目标/类:目标/库:$ JAVA_HOME / jmods 
  •   --add-modules com.mycompany.app 
  •   --launcher app = com.mycompany.app / com.mycompany.app.App 
  •   -输出测试 

您可以向Google的叔叔询问其他参数,所有这些参数都是为了减小捆绑包的总尺寸。

让我们看一下结果:



在test / bin / app内部,有一个简单的sh脚本可以在Java旁边的应用程序上启动我们的应用程序:

 #!/bin/sh JLINK_VM_OPTIONS="-Diterations=10" #     ,       DIR=`dirname $0` $DIR/java $JLINK_VM_OPTIONS -m com.mycompany.app/com.mycompany.app.App $@ 

在C2上运行test / bin / app

 ./test/bin/app In 2017 I would like to run ALL languages in one VM. 

结果
-迭代1-1(315毫秒)
2(231毫秒)
3(214毫秒)
4(297毫秒)
5(257毫秒)
6(211毫秒)
7(217毫秒)
8(245毫秒)
9(222毫秒)
总计:69999993(2424毫秒)
-迭代2-1(215毫秒)
2(215毫秒)
3(223毫秒)
4(224毫秒)
5(217毫秒)
6(208毫秒)
7(208毫秒)
8(222毫秒)
9(222毫秒)
总计:69999993(2164毫秒)
-迭代3-1(206毫秒)
2(226毫秒)
3(234毫秒)
4(211毫秒)
5(212毫秒)
6(213毫秒)
7(210毫秒)
8(245毫秒)
9(223毫秒)
总计:69999993(2216毫秒)
-迭代4-1(222毫秒)
2(233毫秒)
3(220毫秒)
4(222毫秒)
5(221毫秒)
6(219毫秒)
7(222毫秒)
8(216毫秒)
9(220毫秒)
总计:69999993(2215毫秒)
-迭代5-1(231毫秒)
2(230毫秒)
3(221毫秒)
4(226毫秒)
5(227毫秒)
6(223毫秒)
7(215毫秒)
8(216毫秒)
9(219毫秒)
总计:69999993(2234毫秒)
-迭代6-1(227毫秒)
2(218毫秒)
3(221毫秒)
4(254毫秒)
5(222毫秒)
6(212毫秒)
7(214毫秒)
8(222毫秒)
9(222毫秒)
总计:69999993(2241毫秒)
-迭代7-1(217毫秒)
2(225毫秒)
3(222毫秒)
4(223毫秒)
5(227毫秒)
6(221毫秒)
7(219毫秒)
8(226毫秒)
9(219毫秒)
总计:69999993(2217毫秒)
-迭代8-1(218 ms)
2(242毫秒)
3(219毫秒)
4(218毫秒)
5(224毫秒)
6(226毫秒)
7(223毫秒)
8(220毫秒)
9(219毫秒)
总计:69999993(2228毫秒)
-迭代9-1(234 ms)
2(218毫秒)
3(217毫秒)
4(217毫秒)
5(225毫秒)
6(222毫秒)
7(216毫秒)
8(226毫秒)
9(214毫秒)
总计:69999993(2212毫秒)
-迭代10-1(226 ms)
2(230毫秒)
3(215毫秒)
4(238毫秒)
5(225毫秒)
6(218毫秒)
7(218毫秒)
8(215毫秒)
9(228毫秒)
总计:69999993(2233毫秒)

现在在graalvm上(通过定义在JLINK_VM_OPTIONS变量中运行所需的标志):

测试/箱/应用程序
 #!/bin/sh JLINK_VM_OPTIONS="-XX:+UnlockExperimentalVMOptions -XX:+UseJVMCICompiler -Diterations=10" DIR=`dirname $0` $DIR/java $JLINK_VM_OPTIONS -m com.mycompany.app/com.mycompany.app.App $@ 


结果:

 Error occurred during initialization of boot layer java.lang.module.FindException: Module jdk.internal.vm.ci not found 

好吧,我们已经航行了……现在,我们回想起我们在模块化环境中使用Java 11,我们将应用程序构建为模块,但是他们没有告诉任何人所使用的模块。 现在该修复它了。

新版本的module-info.java:

 module com.mycompany.app { requires jdk.internal.vm.compiler; requires org.graalvm.sdk; requires org.graalvm.truffle; requires transitive org.graalvm.js; requires transitive org.graalvm.js.scriptengine; } 

我们收集 ,删除测试目录, 链接

结果:

 Error: automatic module cannot be used with jlink: icu4j from file:///home/slava/JavaProjects/graal-js-jdk11-maven-demo/target/lib/icu4j-62.1.jar 

什么样的“自动模块kennote bi uzd”? 而且此jlink告诉我们icu4j 不包含module-info.class。 此类需要出现在指定的lib中是什么:

  • 了解任何模块使用的模块列表并创建module-info.java ,定义应该从外部可见的所有软件包
  • 编译module-info.java为
  • 将已编译的module-info.java与任何其他文件一起放入dzharnik

走吧

包含所有内容的module-info.java文件将为我们从openjdk-11生成jdeps实用程序:



我们为icu4j编译module-info.java:



我们通过将module-info.class推入dzharnik来更新它:

 $JAVA_HOME/bin/jar uf target/lib/icu4j-62.1.jar -C target/modules module-info.class 

链接运行

结果
-迭代1-1(1216 ms)
2(223毫秒)
3(394毫秒)
4(138毫秒)
5(116毫秒)
6(102毫秒)
7(120毫秒)
8(106毫秒)
9(110毫秒)
总计:69999993(2619毫秒)
-迭代2-1(166毫秒)
2(133毫秒)
3(142毫秒)
4(157毫秒)
5(119毫秒)
6(134毫秒)
7(153毫秒)
8(95毫秒)
9(85毫秒)
总计:69999993(1269毫秒)
-迭代3-1(86毫秒)
2(81毫秒)
3(87毫秒)
4(83毫秒)
5(85毫秒)
6(100毫秒)
7(87毫秒)
8(83毫秒)
9(85毫秒)
总计:69999993(887毫秒)
-迭代4-1(84毫秒)
2(86毫秒)
3(88毫秒)
4(91毫秒)
5(85毫秒)
6(88毫秒)
7(87毫秒)
8(85毫秒)
9(85毫秒)
总计:69999993(864毫秒)
-迭代5-1(94毫秒)
2(86毫秒)
3(84毫秒)
4(83毫秒)
5(85毫秒)
6(86毫秒)
7(84毫秒)
8(84毫秒)
9(83毫秒)
总计:69999993(854毫秒)
-迭代6-1(83毫秒)
2(89毫秒)
3(87毫秒)
4(87毫秒)
5(86毫秒)
6(86毫秒)
7(91毫秒)
8(86毫秒)
9(85毫秒)
总计:69999993(865毫秒)
-迭代7-1(87毫秒)
2(86毫秒)
3(88毫秒)
4(90毫秒)
5(91毫秒)
6(87毫秒)
7(85毫秒)
8(85毫秒)
9(86毫秒)
总计:69999993(868毫秒)
-迭代8-1(84毫秒)
2(85毫秒)
3(86毫秒)
4(84毫秒)
5(84毫秒)
6(88毫秒)
7(85毫秒)
8(86毫秒)
9(86毫秒)
总计:69999993(852毫秒)
-迭代9-1(83 ms)
2(85毫秒)
3(84毫秒)
4(85毫秒)
5(89毫秒)
6(85毫秒)
7(88毫秒)
8(86毫秒)
9(83毫秒)
总计:69999993(850毫秒)
-迭代10-1(83毫秒)
2(84毫秒)
3(83毫秒)
4(82毫秒)
5(85毫秒)
6(83毫秒)
7(84毫秒)
8(94毫秒)
9(93毫秒)
总计:69999993(856毫秒)

万岁! 我们做到了! 现在,我们有了以Java运行的sh-script形式的被禁止的应用程序,其中包含所有必要的模块(包括新鲜的graalvm),并带有优先级和年轻女士。

聚苯乙烯


Java不会感到无聊,而是在每个发行版中为您提供新的思路。 尝试新功能,进行实验,分享经验。 我希望我能尽快写一篇文章,介绍如何禁止我的宠物项目的一部分(有vert.x,异步和js脚本-这将很有趣)。

但是……这是我关于哈布雷的第一篇文章,请不要用力。

Source: https://habr.com/ru/post/zh-CN445978/


All Articles