📜 ⬆️ ⬇️

We cross with the hedgehog: OpenJDK-11 + GraalVM

Hi, Habr! In the light of not the oldest news about the Oracle policy regarding licensing of Java, the issue of avoiding Oracle versions in the direction of OpenJDK is becoming more acute. Odanko in OracleLabs has long been doing a very cool thing called GraalVM , which is a cool JIT compiler written in Java, as well as runtime to run code in languages ​​such as JavaScript, Ruby, Python, C, C ++, Scala, Kotlin, R, Clojure. Impressive, right? But not about the coolness of the polyglot environment, I want to tell you. It will be about the complexity of sticking the latest build of the grail into the OpenJDK 11 ecosystem and a bit about performance, quite a bit ...

First there was the word


The story of my acquaintance with graalvm began on the joker in 2017. There, Chris Seaton spoke in great detail about the insides of the compiler and showed the AOT magic of compilation on the example of using native-image from the delivery of the grail (this is such a joke that compiles your Java code into the native binary).

After that report, I had been training for a very long time to compile the native binaries of my pet project, put crutches and rakes in order to start reflection in all places (if it’s not okay!) And finally came across unresolved IO problems (something it didn’t take off with zookeeper there, now I don’t remember what). I spat on native-image :-(

Year 2018, the same joker and the same graalvm in the super-detailed report from Oleg Shelayev about AOT.
')
Everything looks so great in reports and presentations, and there is a pet project on the disk ... it's time to uncover the terminal, swing the fresh release candidate Grail and go into battle! We'll touch jit.

Hello world


Touching and kicking fresh JIT (at the time of writing this article is version ce-1.0.0-rc14 ) we will use the example of a piece of code to test performance from https://graalvm.org - our first example.

What is the java project (even Hello World ) without any build system? That's right, only the one on which they learn to cook javac. Dzhavak we will not cook, let Dzhavakom taxis maven.

So, meet, 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> 


The project file structure looks like this:



The class code is com.mycompany.app.App (copy-paste of the example from 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); } } } 


Code module-info.java :

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


Hmm, empty ... And it is empty for the reason that I want to show you how custom java (about custom java later) will be cursed with undeclared modules that are needed by our application.

The first pancake ...


not lumpy Let's build our project and run it. Putting it like this:

 mvn clean package 


Run:

 $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. 

There are two important points here: JVMCI is an experimental piece that appeared in Java from version 9, so we need:


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

What do we see here? And we see that the first iteration is the longest (3.5 seconds), this JIT warms up. And then everything is more or less smooth (within one second).

And what if we give Java the latest version of the Grail? No sooner said than done:

 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. 

Fresh Grail Run Result
- iteration 1 - 1 (1789 ms)
2 (547 ms)
3 (313 ms)
4 (87 ms)
5 (88 ms)
6 (87 ms)
7 (83 ms)
8 (92 ms)
9 (87 ms)
total: 69999993 (3259 ms)
- iteration 2 - 1 (241 ms)
2 (161 ms)
3 (152 ms)
4 (195 ms)
5 (136 ms)
6 (129 ms)
7 (154 ms)
8 (176 ms)
9 (109 ms)
total: 69999993 (1553 ms)
- iteration 3 - 1 (109 ms)
2 (103 ms)
3 (113 ms)
4 (172 ms)
5 (141 ms)
6 (148 ms)
7 (111 ms)
8 (102 ms)
9 (101 ms)
total: 69999993 (1211 ms)
- iteration 4 - 1 (96 ms)
2 (96 ms)
3 (104 ms)
4 (98 ms)
5 (96 ms)
6 (97 ms)
7 (98 ms)
8 (96 ms)
9 (95 ms)
total: 69999993 (972 ms)
- iteration 5 - 1 (97 ms)
2 (93 ms)
3 (99 ms)
4 (97 ms)
5 (97 ms)
6 (97 ms)
7 (95 ms)
8 (98 ms)
9 (94 ms)
total: 69999993 (965 ms)
- iteration 6 - 1 (96 ms)
2 (95 ms)
3 (96 ms)
4 (99 ms)
5 (102 ms)
6 (94 ms)
7 (99 ms)
8 (115 ms)
9 (109 ms)
total: 69999993 (1001 ms)
- iteration 7 - 1 (98 ms)
2 (96 ms)
3 (99 ms)
4 (98 ms)
5 (118 ms)
6 (98 ms)
7 (95 ms)
8 (99 ms)
9 (116 ms)
total: 69999993 (1017 ms)
- iteration 8 - 1 (95 ms)
2 (99 ms)
3 (99 ms)
4 (106 ms)
5 (101 ms)
6 (101 ms)
7 (93 ms)
8 (97 ms)
9 (108 ms)
total: 69999993 (993 ms)
- iteration 9 - 1 (102 ms)
2 (95 ms)
3 (97 ms)
4 (125 ms)
5 (94 ms)
6 (101 ms)
7 (100 ms)
8 (95 ms)
9 (96 ms)
total: 69999993 (1008 ms)
- iteration 10 - 1 (97 ms)
2 (97 ms)
3 (99 ms)
4 (112 ms)
5 (102 ms)
6 (96 ms)
7 (96 ms)
8 (98 ms)
9 (96 ms)
total: 69999993 (988 ms)

The result, as we see, is not very different.

I forgot. We did not try to run the same thing without the new JIT compiler. We will do:

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

Results without a modern JIT compiler
- iteration 1 - 1 (372 ms)
2 (271 ms)
3 (337 ms)
4 (391 ms)
5 (328 ms)
6 (273 ms)
7 (239 ms)
8 (271 ms)
9 (250 ms)
total: 69999993 (2978 ms)
- iteration 2 - 1 (242 ms)
2 (253 ms)
3 (253 ms)
4 (240 ms)
5 (245 ms)
6 (275 ms)
7 (273 ms)
8 (263 ms)
9 (234 ms)
total: 69999993 (2533 ms)
- iteration 3 - 1 (237 ms)
2 (235 ms)
3 (234 ms)
4 (246 ms)
5 (242 ms)
6 (238 ms)
7 (244 ms)
8 (243 ms)
9 (253 ms)
total: 69999993 (2414 ms)
- iteration 4 - 1 (244 ms)
2 (249 ms)
3 (245 ms)
4 (243 ms)
5 (232 ms)
6 (256 ms)
7 (321 ms)
8 (303 ms)
9 (249 ms)
total: 69999993 (2599 ms)
- iteration 5 - 1 (246 ms)
2 (242 ms)
3 (248 ms)
4 (256 ms)
5 (280 ms)
6 (233 ms)
7 (235 ms)
8 (266 ms)
9 (246 ms)
total: 69999993 (2511 ms)
- iteration 6 - 1 (292 ms)
2 (368 ms)
3 (339 ms)
4 (251 ms)
5 (267 ms)
6 (259 ms)
7 (289 ms)
8 (262 ms)
9 (357 ms)
total: 69999993 (3058 ms)
- iteration 7 - 1 (284 ms)
2 (258 ms)
3 (248 ms)
4 (247 ms)
5 (266 ms)
6 (247 ms)
7 (242 ms)
8 (314 ms)
9 (265 ms)
total: 69999993 (2656 ms)
- iteration 8 - 1 (239 ms)
2 (238 ms)
3 (257 ms)
4 (282 ms)
5 (244 ms)
6 (261 ms)
7 (253 ms)
8 (295 ms)
9 (256 ms)
total: 69999993 (2575 ms)
- iteration 9 - 1 (273 ms)
2 (243 ms)
3 (239 ms)
4 (240 ms)
5 (250 ms)
6 (285 ms)
7 (266 ms)
8 (285 ms)
9 (264 ms)
total: 69999993 (2617 ms)
- iteration 10 - 1 (245 ms)
2 (264 ms)
3 (258 ms)
4 (253 ms)
5 (239 ms)
6 (260 ms)
7 (251 ms)
8 (250 ms)
9 (256 ms)
total: 69999993 (2538 ms)

The result is different and decent.

C2 does not give any optimizations on hot pieces of code - each iteration with the same time.

Graal is able to optimize hot chunks of code and in the long term gives a good performance boost.

For what?


This is probably the main question that you need to ask yourself and others (team members) when you want to add a new feature, a new tool, a new virtual machine, a new JIT to the project ...

My story, as stated above, began with Joker 2017, then there were many attempts to master AOT, and now I taste JIT's charms for Java on java.

A pet project on disk is a kind of business process engine, where processes are drawn by application developers in the UI in the browser, and they have the opportunity to write scripts in JS that will run on the JVM.

In future versions, Java promises to remove nashorn, GraalVM is gradually nearing release ...

Well, the answer to the question is:

  1. we want runtime for start of JS (and not only)
  2. want speed jit
  3. we want the launch of pet-project applications to be in appearance as before on 8-ke (without
      --module-path 
    and
      --upgrade-module-path 
    but with a fresh build of grail)

Jlink


The first two points in the list of answers to the above question are clear enough, let's deal with the last.

The fact is that developers, admins, devops, people are lazy and do not like doing extra work (I am also like this), they are trying to automate everything and pack it into a ready-made bundle, which can be run as a simple binary. Well, the problem is, let's solve it.

A relatively new tool from the world of Java 9+ comes to our aid, and its name is jlink . We try to pack our application with all the necessary libs in the bundle:

 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 

How many parameters of all sorts, we describe the main:

You can ask Google’s uncle about the remaining parameters, all of which are aimed at reducing the final size of the bundle.

Let's look at the result:



Inside the test / bin / app is a simple sh-script that runs our application on the java that is next to the app:

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

Run test / bin / app on C2:

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

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

Now on graalvm (by defining the necessary flags to run in the variable JLINK_VM_OPTIONS ):

test / bin / app
 #!/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 $@ 


Result:

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

Well, they came ... And now let's remember that we are working with java 11 in a modular environment, we are building the application as a module, and haven't told anyone about the modules we used. It is time to be corrected.

New version of 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; } 

We collect , delete the test directory, link it .

Result:

 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 

What kind of "automatic module kennot biusd"? And this jlink tells us that either icu4j does not contain module-info.class. What is needed for such a class to appear inside the specified location:


Go!

The module-info.java file with all its contents will be generated by jdeps from the openjdk-11 for us:



Compile module-info.java for any icu4j:



Update the jarnik by pushing module-info.class into it:

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

Link , run .

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

HOORAY! We made it! Now we have a banned application in the form of a running sh-script with its java, with all the necessary modules (including fresh graalvm), with preference and young ladies.

PS


Java does not get bored and gives new food for the mind with each release. Try new features, experiment, share experiences. I hope I’m going to write an article soon about how I messed up part of a pet project with a grail (there vert.x, asynchrony and js-scripts will be interesting).

And yet ... this is my first article on Habré, please do not hit hard.

Source: https://habr.com/ru/post/445978/


All Articles