Статьи

Играя с GraalVM

Название изображения

Недавно я начал играть с  Graal VM.  Что такое Грааль ВМ? Чтобы процитировать их сайт напрямую:

GraalVM — это универсальная виртуальная машина для запуска приложений, написанных на языках JavaScript, Python, Ruby, R, JVM, таких как Java, Scala, Kotlin, Clojure, и языков на основе LLVM, таких как C и C ++.

GraalVM устраняет изоляцию между языками программирования и обеспечивает совместимость в общей среде выполнения.

С точки зрения непрофессионала, Graal VM позволяет языкам JavaScript, Python, Ruby, R, JVM-языкам, таким как Java, Scala, Kotlin, Clojure,  общаться друг с другом , тем самым устраняя изоляцию между этими языками.

Но, как разработчик платформы Java / JVM, меня больше интересует способность Graal VM заранее скомпилировать Java для запуска приложения Java как собственного приложения. Дополнительно:

«GraalVM Native Image  позволяет вам заблаговременно скомпилировать код Java в отдельный исполняемый файл, называемый 
собственным образом ».

"native-image это утилита, которая обрабатывает все классы вашего приложения и их зависимости, в том числе из JDK. Он статически анализирует эти классы, чтобы определить, какие классы и методы достижимы и используются во время выполнения приложения. Затем он передает весь этот достижимый код в качестве входных данных компилятору GraalVM, который заранее компилирует его в собственный двоичный файл ».

Ключевые преимущества компиляции Java-приложения AOT в собственный двоичный код:

  1. Скорость запуска
  2. Уменьшенный объем памяти

Например, вот приложение Micronaut:  https://github.com/RaviH/graal-micronaut

Когда я пытаюсь запустить флягу, для запуска требуется около 1,3 с:

 $ java -jar target/graal-micronaut-0.1.jar
 18:30:22.244 [main] INFO  io.micronaut.runtime.Micronaut - Startup completed in 1320ms. Server Running: http://localhost:8080

Но когда я создаю собственный образ, он запускается через  29 мс  (против 1,3 с) — в 45 раз быстрее время загрузки.

# rhasija ~/dev/projects/tmp/graal-micronaut on git:master x  C:130
$ native-image -jar target/graal-micronaut-0.1.jar
Build on Server(pid: 5608, port: 50229)
[graal-micronaut:5608]    classlist:   3,788.29 ms
[graal-micronaut:5608]        (cap):   1,547.58 ms
[graal-micronaut:5608]        setup:   2,041.59 ms
[graal-micronaut:5608]   (typeflow):  27,169.03 ms
[graal-micronaut:5608]    (objects):  32,584.30 ms
[graal-micronaut:5608]   (features):   1,562.28 ms
[graal-micronaut:5608]     analysis:  63,945.36 ms
[graal-micronaut:5608]     universe:   1,593.43 ms
[graal-micronaut:5608]      (parse):   4,368.97 ms
[graal-micronaut:5608]     (inline):   9,400.59 ms
[graal-micronaut:5608]    (compile):  36,820.75 ms
[graal-micronaut:5608]      compile:  53,848.37 ms
[graal-micronaut:5608]        image:   4,955.53 ms
[graal-micronaut:5608]        write:   1,971.52 ms
[graal-micronaut:5608]      [total]: 132,379.50 ms

# rhasija ~/dev/projects/tmp/graal-micronaut on git:master x
$ ./graal-micronaut
18:36:13.776 [main] INFO  io.micronaut.runtime.Micronaut - Startup completed in 29ms. Server Running: http://localhost:8080

Кроме того, исходный образ занял 14 МБ памяти (по сравнению с 387 МБ на основе Java-приложения) — примерно 3% памяти, занимаемой Java-приложением. Это круто.

Java-приложение Micronaut:
13068 java 0.1 00:03.00 23 1 84 387M 0B 0B 13068 5096

Родное изображение:
13136 graal-micron 0.0 00:00.03 3 1 33 14M 0B 0B 13136 5096

Ниже приведены показатели производительности для конечной точки:

Для родного двоичного файла Graal VM:

$ bombardier -n 100000 -c 100 "http://localhost:8080/simple"
Bombarding http://localhost:8080/simple with 100000 request(s) using 100 connection(s)
 100000 / 100000 [=========================================================] 100.00% 18s
Done!
Statistics        Avg      Stdev        Max
  Reqs/sec      5308.20    1376.18    9776.58
  Latency       18.70ms    23.93ms   305.78ms
  HTTP codes:
    1xx - 0, 2xx - 100000, 3xx - 0, 4xx - 0, 5xx - 0
    others - 0
  Throughput:    16.31MB/s%


$ bombardier -n 100000 -c 100 "http://localhost:8080/simple"
Bombarding http://localhost:8080/simple with 100000 request(s) using 100 connection(s)
 100000 / 100000 [=============================================================================================] 100.00% 19s
Done!
Statistics        Avg      Stdev        Max
  Reqs/sec      5164.61    1269.64   11265.72
  Latency       19.22ms    19.82ms   249.97ms
  HTTP codes:
    1xx - 0, 2xx - 100000, 3xx - 0, 4xx - 0, 5xx - 0
    others - 0
  Throughput:    15.87MB/s%


$ bombardier -n 100000 -c 100 "http://localhost:8080/simple"
Bombarding http://localhost:8080/simple with 100000 request(s) using 100 connection(s)
 100000 / 100000 [=============================================================================================] 100.00% 19s
Done!
Statistics        Avg      Stdev        Max
  Reqs/sec      5041.83    1283.07   10297.43
  Latency       19.66ms    22.36ms   310.20ms
  HTTP codes:
    1xx - 0, 2xx - 100000, 3xx - 0, 4xx - 0, 5xx - 0
    others - 0
  Throughput:    15.51MB/s%  

Для приложения Java :

$ bombardier -n 100000 -c 100 "http://localhost:8080/simple"
Bombarding http://localhost:8080/simple with 100000 request(s) using 100 connection(s)
 100000 / 100000 [=============================================================================================] 100.00% 17s
Done!
Statistics        Avg      Stdev        Max
  Reqs/sec      5629.80    1057.21   13696.04
  Latency       17.70ms     8.91ms   182.28ms
  HTTP codes:
    1xx - 0, 2xx - 100000, 3xx - 0, 4xx - 0, 5xx - 0
    others - 0
  Throughput:    17.15MB/s%

  $ bombardier -n 100000 -c 100 "http://localhost:8080/simple"
Bombarding http://localhost:8080/simple with 100000 request(s) using 100 connection(s)
 100000 / 100000 [=============================================================================================] 100.00% 14s
Done!
Statistics        Avg      Stdev        Max
  Reqs/sec      7034.21     592.21   15347.18
  Latency       14.22ms     2.91ms    65.69ms
  HTTP codes:
    1xx - 0, 2xx - 100000, 3xx - 0, 4xx - 0, 5xx - 0
    others - 0
  Throughput:    21.45MB/s%


  # rhasija ~/dev/projects/tmp/graal-micronaut on git:master x
$ bombardier -n 100000 -c 100 "http://localhost:8080/simple"
Bombarding http://localhost:8080/simple with 100000 request(s) using 100 connection(s)
 100000 / 100000 [=============================================================================================] 100.00% 14s
Done!
Statistics        Avg      Stdev        Max
  Reqs/sec      7060.21    2076.69   61322.98
  Latency       14.31ms     2.79ms    57.63ms
  HTTP codes:
    1xx - 0, 2xx - 100000, 3xx - 0, 4xx - 0, 5xx - 0
    others - 0
  Throughput:    21.31MB/s%

Приложение Java работало стабильно лучше.

Детали :

$ mvn -v
Apache Maven 3.6.0 (97c98ec64a1fdfee7767ce5ffb20918da4f719f3; 2018-10-24T11:41:47-07:00)
Maven home: /Users/rhasija/.sdkman/candidates/maven/current
Java version: 1.8.0_202, vendor: Oracle Corporation, runtime: /Users/rhasija/.sdkman/candidates/java/1.0.0-rc-16-grl/jre
Default locale: en_US, platform encoding: UTF-8
OS name: "mac os x", version: "10.13.6", arch: "x86_64", family: "mac"

Чтобы получить больше информации об этом, я попросил людей GraalVM, и Олег Селаев вернулся с этой информацией:

«Native Image действительно предлагает превосходные издержки запуска / памяти? Режим JVM. «

«Кроме того, если вы используете GraalVM Enterprise, вы можете обеспечить --pgo-instrument создание инструментированного двоичного файла  и использовать его профиль для создания (с  --pgo) более производительного собственного образа.

Нижняя граница

Я могу ошибаться, но это то, что я узнал: GraalVM отлично подходит для приложений быстрого запуска / lamdas, которые выполнят свою работу и сразу же будут закрыты, или для приложений, где производительность не является проблемой. Но для долго работающих приложений, которые должны работать с высокой нагрузкой, Java-приложения (из-за JIT-компилятора) все же лучше. Это может измениться в будущем, но сейчас, похоже, это так.

Мысли / обратная связь? Позвольте мне знать в комментариях ниже.