[JAVA] Exécutez Scala avec GraalVM et créez-en une image native

Présentation et utilisation de GraalVM du point de vue d'un programmeur Scala.

GraalVM?

GraalVM semble être capable (parfois) d'accélérer les programmes Scala (Java) si vous franchissez le pas.

Si vous lisez cette zone, elle est écrite un peu plus en détail. https://www.graalvm.org/docs/why-graal/#for-java-programs

Pour résumer brièvement, les fonctionnalités sont

  1. Accélérez Java Il y a quelques améliorations dans la compilation JIT.
  2. Exécutez un autre langage (JS ou Python) dans le contexte Java Peut développer des applications Polyglot
  3. Créez une image native Compilation AOT au lieu de JIT

Trois points sont énumérés.

Dans cet article, je vais aborder 1 et 3. Voir Intégrer les langues avec l'API Graal Polyglot pour 2.

La version de GraalVM est CE-1.0.0-rc9.

Installation

Depuis qu'il a été téléchargé sur Github, téléchargez-le et décompressez-le. [Releases · oracle/graal](https://github.com/oracle/graal/releases

$ wget https://github.com/oracle/graal/releases/download/vm-1.0.0-rc9/graalvm-ce-1.0.0-rc9-macos-amd64.tar.gz 
$ tar zxvf graalvm-ce-1.0.0-rc9-macos-amd64.tar.gz
$ export GRAALVM_HOME=$PWD/graalvm-ce-1.0.0-rc9

De plus, une variable d'environnement appelée GRAALVM_HOME est définie.

Accélérez votre programme avec GraalVM

C'est très facile à utiliser, il suffit d'installer et d'utiliser au lieu de Java.

$ java -jar main.jar #Exécuter avec Oracle JDK
$ $GRAALVM_HOME/bin/java -jar main.jar #Exécuter sur Graal VM

Utilisez sbt-jmh pour vérifier les performances pour voir si cela seul va vraiment accélérer. Le code de vérification des performances est compté à partir de la chaîne de caractères en se référant aux Démos Graal VM: Graal Performance Examples for Java. J'ai essayé de le faire. Un exemple de code est disponible sur Github.

package net.petitviolet.example

import org.openjdk.jmh.annotations._

@State(Scope.Thread)
class ForBench {
  private val sentence = "In 2017 I would like to run ALL languages in one VM."
  private val answer = 7

  @Benchmark
  @BenchmarkMode(Array(Mode.Throughput))
  def bench_upperCaseCount() = {
    upperCaseCount(sentence)
  }

  private def upperCaseCount(args: String) = {
    val sentence = String.join(" ", args)
    require(sentence.filter(Character.isUpperCase).length == answer)
  }
}

Exécutez jmh sur ce code. Vous pouvez éventuellement passer -jvm à la tâche jmh: run, alors essayez d'exécuter le benchmark lors du changement.

Tout d'abord, Oracle JDK 1.8.0_192. La sortie résultante est éclaircie de manière appropriée.

sbt:main> jmh:run -i 10 -wi 10 -f1 -t 1 -jvm /path/to/java/1.8/bin/java
[info] Running (fork) org.openjdk.jmh.Main -i 10 -wi 10 -f1 -t 1 -jvm /path/to/java/1.8/bin/java
[info] # JMH version: 1.21
[info] # VM version: JDK 1.8.0_192, Java HotSpot(TM) 64-Bit Server VM, 25.192-b12
[info] # VM invoker: /path/to/java/1.8/bin/java
[info] # Benchmark mode: Throughput, ops/time
[info] # Benchmark: net.petitviolet.example.ForBench.bench_upperCaseCount
[info] Benchmark                       Mode  Cnt        Score        Error  Units
[info] ForBench.bench_upperCaseCount  thrpt   10  2548794.689 ± 212771.778  ops/s

Puis exécutez avec GraalVM 1.0.0-rc9.

sbt:main> jmh:run -i 10 -wi 10 -f1 -t 1 -jvm /path/to/graalvm-ce-1.0.0-rc9/Contents/Home/bin/java
[info] Running (fork) org.openjdk.jmh.Main -i 10 -wi 10 -f1 -t 1 -jvm /path/to/graalvm-ce-1.0.0-rc9/Contents/Home/bin/java
[info] # JMH version: 1.21
[info] # VM version: JDK 1.8.0_192, GraalVM 1.0.0-rc9, 25.192-b12-jvmci-0.49
[info] # *** WARNING: JMH support for this VM is experimental. Be extra careful with the produced data.
[info] # Benchmark mode: Throughput, ops/time
[info] # Benchmark: net.petitviolet.example.ForBench.bench_upperCaseCount
[info] # Run progress: 0.00% complete, ETA 00:03:20
[info] Benchmark                       Mode  Cnt        Score       Error  Units
[info] ForBench.bench_upperCaseCount  thrpt   10  2904523.828 ± 28572.650  ops/s
[success] Total time: 203 s, completed Nov 25, 2018 9:46:55 PM

En comparant simplement les résultats, GraalVM était un peu plus rapide.

résultat OracleJDK GraalVM
Throughput 2548794.689 2904523.828

Cependant, cette fois, c'est un code simple, il est donc possible que GraalVM soit plus avantageux, mais au moins il y a des cas où le simple fait d'utiliser GraalVM comme celui-ci le rend plus rapide.

NativeImage

Ensuite, au lieu de la compilation JIT, la compilation AOT est utilisée pour cracher du code natif, c'est-à-dire des binaires exécutables. SubstrateVM est la technologie qui le prend en charge.

Substrate VM is a framework that allows ahead-of-time (AOT) compilation of Java applications under closed-world assumption into executable images or shared objects (ELF-64 or 64-bit Mach-O).

Il y a Scala Native dans Scala, mais je ne le mentionnerai pas ici.

Tout d'abord, rendez «native-image» utilisable. Cependant, si vous l'avez déjà téléchargé, vous pouvez simplement le mettre dans votre PATH.

$ export PATH=$PATH:$GRAALVM_HOME/Contents/Home/bin
$ native-image --version
GraalVM Version 1.0.0-rc9

Il peut être utilisé pour convertir des programmes Scala (Java) en code natif. Plus précisément, vous pouvez donner fat-JAR à l'argument de la commande native-image. Si vous utilisez sbt, vous pouvez facilement générer du fat-JAR en utilisant sbt-assembly, afin de pouvoir l'utiliser.

Pour l'exécuter, tapez la commande suivante.

$ native-image \
  -jar main.jar \ # sbt-La graisse crache dans l'assemblage-Spécifiez JAR
  -H:IncludeResources=".*.xml|.*.conf" \
  -H:+ReportUnsupportedElementsAtRuntime \
  -H:Name=app \ #Chemin binaire de sortie
  --verbose

Pour l'argument de -jar, spécifiez le fat-JAR craché par sbt-assembly. Avec -H: IncludeResources, vous pouvez spécifier le fichier de ressources à inclure dans le binaire avec une expression régulière. Cette fois, je voulais inclure logback.xml et application.conf, alors je l'ai spécifié.

Au fait, si vous utilisez -cp au lieu de -jar, vous pouvez le faire sans fat-JAR, mais il est recommandé car il est moins mémorable. Voir Options de génération d'image car les autres commandes sont facultatives.

Que se passe-t-il si vous convertissez avec NativeImage

Ce qui me rend heureux d'être natif, c'est que la startup est explosive. Java améliore de toute façon considérablement le spin-up lourd de JVM.

Le code source qui s'exécute dans cet exemple est [scala_graalvm_prac / Application.scala](https://github.com/petitviolet/scala_graalvm_prac/blob/adad5c144534d473e5b61de38ec5e86f6d809172/modules/net/rc/petala/petolet/application J'utilise celui de scala), et c'est une application qui démarre et quitte simplement une application Web qui utilise http4s.

Tout d'abord, essayez de l'exécuter en java à des fins de comparaison.

$ /usr/bin/time java -jar main.jar TimeTest
2018-11-26 17:24:29.129 INFO  [main][o.h.b.c.n.NIO1SocketServerGroup] - Service bound to address /0:0:0:0:0:0:0:0:8080
2018-11-26 17:24:29.135 INFO  [main][o.h.s.b.BlazeBuilder] -   _   _   _        _ _
2018-11-26 17:24:29.135 INFO  [main][o.h.s.b.BlazeBuilder] -  | |_| |_| |_ _ __| | | ___
2018-11-26 17:24:29.135 INFO  [main][o.h.s.b.BlazeBuilder] -  | ' \  _|  _| '_ \_  _(_-<
2018-11-26 17:24:29.135 INFO  [main][o.h.s.b.BlazeBuilder] -  |_||_\__|\__| .__/ |_|/__/
2018-11-26 17:24:29.135 INFO  [main][o.h.s.b.BlazeBuilder] -              |_|
2018-11-26 17:24:29.230 INFO  [main][o.h.s.b.BlazeBuilder] - http4s v0.18.9 on blaze v0.12.13 started at http://[0:0:0:0:0:0:0:0]:8080/
2018-11-26 17:24:29.230 INFO  [main][n.p.e.Application] - start server.
2018-11-26 17:24:29.230 INFO  [main][n.p.e.Application] - start shutting down immediately.
2018-11-26 17:24:29.235 INFO  [main][o.h.b.c.ServerChannel] - Closing NIO1 channel /0:0:0:0:0:0:0:0:8080 at Mon Nov 26 17:24:29 JST 2018
2018-11-26 17:24:29.237 INFO  [main][o.h.b.c.n.NIO1SocketServerGroup] - Closing NIO1SocketServerGroup
2018-11-26 17:24:29.237 INFO  [main][n.p.e.Application] - shutting down completed.
        1.74 real         1.84 user         0.20 sys

Le résultat.

Ensuite, essayez d'exécuter le binaire créé avec native-image.

$ /usr/bin/time ./app TimeTest
2018-11-26 17:24:37.190 INFO  [main][o.h.b.c.n.NIO1SocketServerGroup] - Service bound to address /0:0:0:0:0:0:0:0:8080
2018-11-26 17:24:37.190 INFO  [main][o.h.s.b.BlazeBuilder] -   _   _   _        _ _
2018-11-26 17:24:37.190 INFO  [main][o.h.s.b.BlazeBuilder] -  | |_| |_| |_ _ __| | | ___
2018-11-26 17:24:37.190 INFO  [main][o.h.s.b.BlazeBuilder] -  | ' \  _|  _| '_ \_  _(_-<
2018-11-26 17:24:37.190 INFO  [main][o.h.s.b.BlazeBuilder] -  |_||_\__|\__| .__/ |_|/__/
2018-11-26 17:24:37.190 INFO  [main][o.h.s.b.BlazeBuilder] -              |_|
2018-11-26 17:24:37.190 INFO  [main][o.h.s.b.BlazeBuilder] - http4s v0.18.9 on blaze v0.12.13 started at http://[0:0:0:0:0:0:0:0]:8080/
2018-11-26 17:24:37.190 INFO  [main][n.p.e.Application] - start server.
2018-11-26 17:24:37.190 INFO  [main][n.p.e.Application] - start shutting down immediately.
2018-11-26 17:24:37.190 INFO  [main][o.h.b.c.ServerChannel] - Closing NIO1 channel /0:0:0:0:0:0:0:0:8080 at Mon Nov 26 17:24:37 JST 2018
2018-11-26 17:24:37.190 INFO  [main][o.h.b.c.n.NIO1SocketServerGroup] - Closing NIO1SocketServerGroup
2018-11-26 17:24:37.190 INFO  [main][n.p.e.Application] - shutting down completed.
        0.03 real         0.01 user         0.01 sys

Presque rien n'a changé dans la sortie de l'application, mais comme elle est de 1,74 s → 0,03 s, on peut voir que le démarrage est clairement plus rapide.

Autres sentiments divers

On dit que Twitter a été mis en production, mais il peut être possible de l'adopter en fonction du résultat de la vérification du fonctionnement / des performances. Un autre avantage est qu'il est facile de rentrer même s'il y a un problème.

Cependant, il est encore trop tôt pour introduire le codage natif en utilisant "native-image".

Les restrictions sont décrites dans LIMITATIONS.md. Le gros problème est que le chargement de classe dynamique n'est pas pris en charge et que certaines réflexions ne sont pas disponibles. Scala utilise souvent des macros, mais cela ne fonctionne souvent pas. Pour cette raison, je ne pouvais pas utiliser Playframework ou Akka-HTTP. Il existe encore de nombreuses restrictions, par exemple lorsque vous essayez d'utiliser l'Appender Async de Logback autre que le framework d'application Web, cela ne fonctionne pas bien. Depuis AOT est juste pré-compilé, il est devenu difficile de vouloir le faire au moment de l'exécution. À propos, il y avait une démo GraalVM qui a converti le compilateur Scala en une image native, vous pouvez donc vous y référer également. graalvm-demos/scala-days-2018/scalac-native at master · graalvm/graalvm-demos

Cependant, cela ne signifie pas qu'il peut être utilisé du tout. Par exemple, cela peut être une option pour faire fonctionner des outils pour CLI avec Scala (Java) + native-image au lieu de Golang.

S'il devient plus polyvalent, il est possible que l'incompatibilité entre l'ère Docker / Kubernetes et le spin-up JVM soit améliorée, donc j'aimerais m'y attendre à l'avenir.

Recommended Posts

Exécutez Scala avec GraalVM et créez-en une image native
Essayez de créer Java dans un module natif avec GraalVM
[Java] Rendez-le constant
Créez un labyrinthe de fouilles avec Ruby2D
Créer un outil de diaporama avec JavaFX
Priority Queue max En faire une file d'attente
Faire un rappel de garbage avec line-bot-sdk-java
Créer une carte de liste avec LazyMap
Faites fonctionner Jupyter Lab n'importe où avec Docker
Faites un jeu de frappe avec ruby
Faisons une carte de Noël avec Processing!
Faites une liste de choses à faire en famille avec Sinatra
Faites une liste de choses à faire en famille avec Sinatra
Exécutez DMN à l'aide du moteur Camunda DMN
Créez quand même une fonction de connexion avec Rails
[docker] [nginx] Créer un ALB simple avec nginx
Exécuter Rust depuis Java avec JNA (Java Native Access)
Exécutez l'application Scala avec Spring Boot via Gradle
Créer Scala Seq à partir de Java, faire de Scala Seq une liste Java
Faisons une fonction de recherche avec Rails (ransack)
Rendre System.out Mock avec Spock Test Framework