Хабрахабр

[Перевод] Десять вещей, которые можно делать с GraalVM

Статья ниже — это не просто перечисление того, что GraalVM умеет, но ещё и небольшой мастер-класс, аналогичный тому, который Chris Seaton и Олег Шелаев проводили на Oracle CodeOne 2018.
От переводчика: GraalVM — новая, интересная технология, но на Хабре по ней не так много статей, которые бы могли показать примеры возможностей Graal. Вслед за автором, призываю — пробуйте делать примеры из статьи, это действительно интересно.

В этой статье мы рассмотрим разнообразные возможности, которые предоставляет GraalVM и покажем, что с их помощью можно сделать. В GraalVM много всего разного, и, если вы слышали это название раньше, или даже видели доклады, то все равно есть много вещей, о которых вы наверняка ещё не знаете, но которые GraalVM может делать.

  1. Быстрое выполнение Java
  2. Уменьшение времени старта и потребления памяти для Java
  3. Комбинирование JavaScript, Java, Ruby и R
  4. Исполнение программ, написанных на платформо-зависимых языках
  5. Общие инструменты для всех языков программирования
  6. Дополнение JVM приложений
  7. Дополнение платформо-зависимых приложений
  8. Код Java как платформо-зависимая библиотека
  9. Поддержка нескольких языков программирования в базе данных
  10. Создание языков программирования для GraalVM

0. Вы можете сделать все то, что показано в этой статье, при помощи GraalVM 1. Я использовал версию Enterprise Edition на MacOS, но код, который здесь написан, будет работать на Linux и на GraalVM Community Edition. 0 RC1, которая доступна по ссылке с сайта GraalVM.

Код можно скачать с GitHub. Когда будете читать статью, запускайте программы, которые в ней описаны!

Установка

По умолчанию это добавляет поддержку выполнения Java и JavaScript. После загрузки с http://graalvm.org/downloads я добавил путь к исполняемым файлам GraalVM в $PATH.

$ git clone https://github.com/chrisseaton/graalvm-ten-things.git
$ cd foo
$ tar -zxf graalvm-ee-1.0.0-rc1-macos-amd64.tar.gz # or graalvm-ee-1.0.0-rc1-linux-amd64.tar.gz on Linux
$ export PATH=graalvm-1.0.0-rc1/Contents/Home/bin:$PATH # or PATH=graalvm-1.0.0-rc1/bin:$PATH on Linux

Я дополнительно установил Ruby, Python и R, они скачиваются с GitHub. GraalVM идет со встроенной поддержкой JavaScript и содержит менеджер пакетов, который называется gu, добавляющий возможность установить поддержку других языков, помимо Java и JavaScript.

$ gu install -c org.graalvm.ruby
$ gu install -c org.graalvm.python
$ gu install -c org.graalvm.R

Теперь, если вы выполните команду java или js, то увидите GraalVM версии этих движков.

$ java -version
java version "1.8.0_161"
Java(TM) SE Runtime Environment (build 1.8.0_161-b12)
GraalVM 1.0.0-rc1 (build 25.71-b01-internal-jvmci-0.42, mixed mode)
$ js --version
Graal JavaScript 1.0 (GraalVM 1.0.0-rc1)

1. Быстрое выполнение Java

Он один создан, чтобы править всеми! “Graal” в GraalVM — это название компилятора. Например, мы используем Graal для компиляции как ahead-of-time, так и just-in-time, чтобы компилировать код, написанный на разных языках программирования, в том числе и для разных архитектур процессоров. Это означает, что это одна реализация компилятора, написанная в виде библиотеки, которая может быть использована для большого количества разных вещей.

Первый, и самый простой способ использования Graal — это использовать его как Java JIT компилятор.

В программе используются возможности современного языка Java, такие как Streams и коллекции. В качестве примера будем использовать программу, которая выдает 10 наиболее часто встречающихся слов в документе.

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.Arrays;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream; public class TopTen private static Stream<String> fileLines(String path) { try { return Files.lines(Paths.get(path)); } catch (IOException e) { throw new RuntimeException(e); } } }

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

$ javac TopTen.java

Я буду использовать команду time для того, чтобы получить реальные данные о времени, которое было потрачено на выполнение программы от начала до конца, вместо того, чтобы разворачивать сложный микробенчмарк. Если мы запустим команду java, которая включена в GraalVM, то JIT компилятор Graal будет использоваться автоматически — не нужно делать никаких дополнительных действий. Объем файла large.txt — 150 Мб. Также будет использоваться большой объем входных данных чтобы не было никаких инсинуаций о сэкономленной паре секунд тут или там.

$ make large.txt
$ time java TopTen large.txt
sed = 502701
ut = 392657
in = 377651
et = 352641
id = 317627
eu = 317627
eget = 302621
vel = 300120
a = 287615
sit = 282613
real 0m17.367s
user 0m32.355s
sys 0m1.456s

Мы думаем, что это позволяет нам улучшать его быстрее, чем существующие компиляторы, дополняя новыми мощными оптимизациями (такими, например, как частичный escape анализ), которые недоступны в стандартном JIT компиляторе для HotSpot.
И это может сделать ваши Java программы значительно быстрее. Graal написан на Java, а не на C++, как большинство остальных JIT компиляторов для Java.

JVMCI — это интерфейс между Graal и JVM. В целях сравнения, чтобы выполнять программы без JIT компилятора Graal, я буду использовать флаг -XX:-UseJVMCICompiler. А ещё можно запустить пример на стандартной JVM и сравнить результаты.

$ time java -XX:-UseJVMCICompiler TopTen large.txt
sed = 502701
ut = 392657
in = 377651
et = 352641
id = 317627
eu = 317627
eget = 302621
vel = 300120
a = 287615
sit = 282613
real 0m23.511s
user 0m24.293s
sys 0m0.579s

Там, где мы считаем, что улучшение производительности на единицы процентов — значительное достижение, 25 % — это большое дело. Этот тест показывает, что Graal выполняет нашу Java программу примерно за три четверти того времени, которое требуется, чтобы выполнить ее со стандартным HotSpot компилятором.

Twitter использует Graal для исполнения приложений, написанных на Scala — Graal работает на уровне JVM байткода, т.е. Twitter — единственная компания, на сегодняшний день, которая использует Graal на “боевых” серверах, и они говорят, что для них это оправдано, в терминах экономии реальных денег. применим для любого JVM языка.

Это первый вариант использования GraalVM — просто замена JIT компилятора на лучшую версию для ваших существующих Java приложений.

2. Уменьшение времени старта и потребления памяти для Java

Короткоживущие процессы, напротив, страдают от долгого времени запуска и относительно большого использования памяти. Сильные стороны платформы Java особенно явно проявляются при работе с долго выполняющимися процессами и пиковыми нагрузками.

Используем параметр -l для распечатки объема используемой памяти в дополнение ко времени исполнения. Например, если мы запустим приложение из предыдущего раздела, подав ему на вход гораздо меньший объем входных данных — около 1Кб вместо 150Мб, то, похоже, это займет неразумно долгое время и довольно много памяти — порядка 60Мб, для того, чтобы обработать такой маленький файл.

$ make small.txt
$ /usr/bin/time -l java TopTen small.txt # -v on Linux instead of -l
sed = 6
sit = 6
amet = 6
mauris = 3
volutpat = 3
vitae = 3
dolor = 3
libero = 3
tempor = 2
suscipit = 2 0.32 real 0.49 user 0.05 sys 59846656 maximum resident set size

Мы сказали, что Graal -это библиотека-компилятор и он может быть использован многими различными способами. GraalVM дает нам инструмент, который решает эту проблему. Это похоже на то, как работает обычный компилятор, например gcc. Один из них — компиляция ahead-of-time в платформозависимый исполняемый образ, вместо компиляции just-in-time во время выполнения.

$ native-image --no-server TopTen classlist: 1,513.82 ms (cap): 2,333.95 ms setup: 3,584.09 ms (typeflow): 4,642.13 ms (objects): 3,073.58 ms (features): 156.34 ms analysis: 8,059.94 ms universe: 353.02 ms (parse): 1,277.02 ms (inline): 1,412.08 ms (compile): 10,337.76 ms compile: 13,776.23 ms image: 2,526.63 ms write: 1,525.03 ms [total]: 31,439.47 ms

Этот файл не запускает JVM, он не слинкован с JVM и он вообще никаким способом не включает в себя JVM. Эта команда создает платформозависимый исполняемый файл, который называется topten. Для компонентов среды выполнения, таких как сборщик мусора, мы запускаем нашу собственную новую VM, которая называется SubstrateVM, которая, как и Graal, также написана на Java. Команда native-image по-настоящему компилирует ваш Java код и Java библиотеки, которые вы используете, в полноценный машинный код.

Мы можем перенести только один этот файл на систему, в которой даже никогда не была установлена JVM и запустить его там, чтобы проверить, что он не использует JVM или какие-либо другие файлы. Если вы посмотрите на зависимости, которые использует topten, то увидите, что это только стандартные системные библиотеки. Topten также достаточно маленький — исполняемый код занимает объем меньше 6 Мб.

$ otool -L topten # ldd topten on Linux
topten: .../CoreFoundation.framework ... .../libz.1.dylib ... .../libSystem.B.dylib ...
$ du -h topten 5.7M topten

Запуск настолько быстр, что вы не заметите, сколько времени это заняло. Если мы запустим этот исполняемый файл, то мы увидим, что он запускается примерно на порядок быстрее и использует примерно на порядок меньше памяти, чем та же программа, запущенная под JVM. Если будете использовать командную строку — вы не почувствуете той паузы, которая обычно присутствует, когда вы запускаете маленькую, короткоживущую программу под JVM

$ /usr/bin/time -l ./topten small.txt
sed = 6
sit = 6
amet = 6
mauris = 3
volutpat = 3
vitae = 3
dolor = 3
libero = 3
tempor = 2
suscipit = 2 0.02 real 0.00 user 0.00 sys 4702208 maximum resident set size

Так, во время компиляции у вас должны присутствовать все классы, также есть ограничения в области использования Reflection API. В утилите native-image есть некоторые ограничения. Таким образом, уменьшается количество работы, выполняемой каждый раз, когда приложение загружается. Зато присутствуют некоторые дополнительные преимущества перед базовой компиляцией, такие как выполнение статических инициализаторов во время компиляции.

Этот способ устраняет такие проблемы с конфигурацией, как поиск нужного jar во время выполнения, а также позволяет создавать Docker образы меньшего размера. Это второе применение GraalVM — распространение и выполнение существующих Java программ, с быстрым стартом и меньшим расходом памяти.

3. Комбинирование JavaScript, Java, Ruby и R

Они написаны с использованием нового фреймворка, который называется Truffle. Наравне с Java, GraalVM включает новые реализации движков JavaScript, Ruby, R и Python. Когда вы пишете интерпретатор какого-либо языка с использованием Truffle, то он будет автоматически использовать Graal, чтобы обеспечить JIT компиляцию для вашего языка. Этот фреймворк делает возможным создание интерпретаторов языков, которые одновременно и простые, и высокопроизводительные. Таким образом, Graal — не только JIT компилятор и AOT компилятор для Java, он также может быть JIT компилятором для JavaScript, Ruby, R и Python.

Например, мы можем установить модуль “color” для Node.js: Поддержка сторонних языков в GraalVM нацелена на то, чтобы быть прозрачной заменой для существующих движков исполнения различных языков.

$ npm install --global color
...
+ color@3.0.0
added 6 packages in 14.156s

Потом написать программу с использованием этого модуля для конвертирования RGB HTML цвета в HSL:

var Color = require('color'); process.argv.slice(2).forEach(function (val) { print(Color(val).hsl().string());
});

И запустить ее привычным способом:

$ node color.js '#42aaf4'
hsl(204.89999999999998, 89%, 60.8%)

А это позволяет вам писать мультиязычные программы — программы, написанные более чем на одном языке программирования. Движки исполнения разных языков в GraalVM работают вместе — есть API, который позволяет вам запускать код из одного языка в программе, написанной на другом языке.

Например, представим, что нам нужно написать приложение для конвертирования имени цвета из CSS в его числовое представление в Node.js, но мы хотим использовать библиотеку работы с цветом, написанную на Ruby, вместо того, чтобы самому писать конвертацию. Это может понадобиться, если вы пишете большую часть своей программы на одном языке, но хотели бы использовать библиотеку, написанную на другом языке программирования.

var express = require('express');
var app = express(); color_rgb = Polyglot.eval('ruby', ` require 'color' Color::RGB
`); app.get('/css/:name', function (req, res) { color = color_rgb.by_name(req.params.name).html() res.send('<h1 style="color: ' + color + '" >' + color + '</h1>');
}); app.listen(8080, function () { console.log('serving at http://localhost:8080')
});

В Ruby мы бы его использовали так: Color::RGB.by_name (name).html. В этом коде мы написали, что надо выполнить Ruby код как строку, но обратите внимание, что мы не делали тут многого — мы всего лишь подключили библиотеки и потом возвратили Ruby объект. И мы передаем их как строки JavaScript и соединяем результат, который является Ruby строкой, с JavaScript строкой. Если вы посмотрите на то, как color_rgb используется далее в JavaScript, то вы увидите, что мы, фактически, вызываем эти же методы из JavaScript, хотя это Ruby объекты и методы.

Установим обе зависимости — Ruby и JavaScript.

$ gem install color
Fetching: color-1.8.gem (100%)
Successfully installed color-1.8
1 gem installed
$ npm install express
+ express@4.16.2
updated 1 package in 10.393s

Затем нужно запустить node с парой дополнительных опций: --polyglot, чтобы сказать, что нам нужен доступ к другим языкам и --jvm, потому что исполняемый образ node по умолчанию не включает в себя ничего, кроме JavaScript.

$ node --polyglot --jvm color-server.js
serving at http://localhost:8080

А потом перейдем на URL http://localhost:8080/css/orange (или какой-нибудь другой цвет), как обычно, в своем браузере.

Давайте попробуем сделать пример посерьезнее, который использует больше языков и модулей.

Я нашел несколько таких модулей, как big-integer, но они все неэффективны, т.к. В JavaScript нет поддержки очень больших целых чисел. Класс BigInteger в Java более эффективен, давайте используем его для того, чтобы сделать несколько арифметических операций с большими целыми. хранят компоненты числа в виде JavaScript чисел с плавающей точкой.

Давайте используем модуль svg из R, чтобы нарисовать точечный график тригонометрической функции в 3D пространстве. В JavaScript также нет никакой встроенной поддержки для рисования графиков, тогда как R превосходно рисует графики.

В обоих случаях мы будем использовать API для поддержки мультиязычности из GraalVM (далее — Polyglot API) и мы сможем просто вставить результаты выполнения программ на других языках в JavaScript.

const express = require('express')
const app = express() const BigInteger = Java.type('java.math.BigInteger') app.get('/', function (req, res) { var text = 'Hello World from Graal.js!<br> ' // Using Java standard library classes text += BigInteger.valueOf(10).pow(100) .add(BigInteger.valueOf(43)).toString() + '<br>' // Using R interoperability to create graphs text += Polyglot.eval('R', `svg(); require(lattice); x <- 1:100 y <- sin(x/10) z <- cos(x^1.3/(runif(1)*5+10)) print(cloud(x~y*z, main="cloud plot")) grDevices:::svg.off() `); res.send(text)
}) app.listen(3000, function () { console.log('Example app listening on port 3000!')
})

Мы представляем это как способ унификации сред выполнения и библиотек — вы можете использовать тот язык программирования, который, как вы думаете, лучше всего подходит для решения текущей задачи и любую библиотеку, которую хотите, вне зависимости от того, на каком языке программирования она написана. Откройте http://localhost:3000/ в своем браузере, чтобы увидеть результат:

Это третья вещь, которую мы можем делать с GraalVM — запускать программы, написанные на нескольких языках и использовать модули из этих языков вместе в одной программе.

4. Исполнение программ, написанных на платформо-зависимых языках

GraalVM может исполнять код на C так же, как он исполняет программы, написанные на JavaScript и Ruby. Ещё один язык, который поддерживает GraalVM — это C.

биткод, а не прямую поддержку C. Что на самом деле GraalVM поддерживает — так это запуск кода, полученного в результате выполнения утилит LLVM, т.е. Для простоты демонстрации я запускаю специальную версию gzip, которая собрана одним файлом (эту версию поддерживает Stephen McCamant). Это значит, что можно использовать существующий инструментарий для языка C и других, которые поддерживают LLVM, таких как С++, Fortran и, потенциально, большее количество языков в будущем. Мне пришлось пропатчить пару вещей, чтобы это заработало на macOS и с clang, но специально для поддержки GraalVM я ничего не делал. Это просто исходный код gzip и конфигурация autoconf, объединенные в один файл, для простоты.

Я использую clang 4. Мы компилируем gzip, используя стандартный clang (компилятор LLVM для С) и хотим, чтобы он сделал нам LLVM биткод, а не платформо-зависимую сборку, потому что ее GraalVM не запустит. 1. 0.

$ clang -c -emit-llvm gzip.c

Давайте попробуем сжать файл, используя мой системный архиватор gzip, а затем разархивировать его, используя gzip, запущенный под GraalVM. И затем запускаем полученный результат, напрямую используя команду lli (интерпретатор биткода LLVM) из GraalVM.

$ cat small.txt
Lorem ipsum dolor sit amet...
$ gzip small.txt
$ lli gzip.bc -d small.txt.gz
$ cat small.txt
Lorem ipsum dolor sit amet...

Это значит, что вы можете запускать эти расширения внутри VM и это позволяет нам сохранять высокую скорость выполнения, даже если мы используем устаревшие платформо-зависимые интерфейсы расширений. Реализации Ruby и Python в GraalVM используют эту же технику для запуска расширений, написанных на C для этих языков.

Это четвертый способ использования GraalVM — запуск программ написанных на платформо-зависимых языках, таких как C или C++, а также запуск расширений к таким языкам, как Python или Ruby, что не в состоянии делать JVM реализации этих языков, такие как JRuby.

5. Общие инструменты для всех языков программирования

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

Это позволяет нам сделать функциональность, например, отладчика, один раз и использовать ее для всех языков. Поддержка всех языков в GraalVM (кроме Java, на текущий момент) реализована с помощью общего фреймворка — Truffle.

Таким образом, нам будет проще расставить точки останова. Чтобы это попробовать, мы напишем простейшую программу — FizzBuzz, потому что она наглядная (печатает что-то на экране) и в ней есть четкие ветвления, которые используются только в некоторых итерациях. Начнем с реализации на JavaScript.

function fizzbuzz(n) { if ((n % 3 == 0) && (n % 5 == 0)) { return 'FizzBuzz'; } else if (n % 3 == 0) { return 'Fizz'; } else if (n % 5 == 0) { return 'Buzz'; } else { return n; }
} for (var n = 1; n <= 20; n++) { print(fizzbuzz(n));
}

Запускаем программу как обычно, используя утилиту js, под GraalVM.

$ js fizzbuzz.js
1
2
Fizz
4
Buzz
Fizz

Это даст нам ссылку, которую можно открыть в Хроме и остановить программу в отладчике. Мы также можем запустить программу с флагом --inspect.

$ js --inspect fizzbuzz.js
Debugger listening on port 9229.
To start debugging, open the following URL in Chrome: chrome-devtools://devtools/bundled/inspector.html?ws=127.0.0.1:9229/6c478d4e-1350b196b409

Когда программа прервет выполнение, мы увидим значение переменной n в отладчике и можем продолжить выполнение программы или поизучать интерфейс отладчика.

Отладчик в Хроме обычно используется для JavaScript, но для GraalVM в JavaScript нет ничего отличного от других языков. Можно поставить точку останова в коде FizzBuzz и затем продолжить выполнение. Я не буду показывать вам исходники каждой программы, но они запускаются точно так же и вы получаете такой же отладчик в Хроме для каждой из них. Флаг --inspect также доступен и работает в реализации Python, Ruby и R.

$ graalpython --jvm --inspect fizzbuzz.py

$ ruby --inspect fizzbuzz.rb

$ Rscript --inspect fizzbuzz.r

Он предоставляет пользовательский интерфейс, используя который вы можете присоединиться к работающей JVM на вашей локальной машине или через сеть, чтобы проинспектировать разные аспекты выполнения программы, такие как использование памяти или потоков выполнения.
Ещё один инструмент, который, возможно, вам знаком из Java — это VisualVM.

GraalVM включает VisualVM в виде стандартной утилиты jvisualvm.

$ jvisualvm &> /dev/null &

Если мы запустим VisualVM в то время, когда у нас работает Java программа TopTen, то мы сможем понаблюдать за использованием памяти или, например, сделать снимок содержимого памяти и посмотреть, какие типы объектов у нас используют память в куче.

$ java TopTen large.txt


Я написал эту программу на Ruby, чтобы сгенерировать немного мусора в памяти во время выполнения.

require 'erb' x = 42 template = ERB.new <<-EOF The value of x is: <%= x %>
EOF loop do puts template.result(binding)
end

Если вы запустите стандартную реализацию Ruby на JVM — JRuby, то будете разочарованы VisualVM, потому что увидите только внутренние Java объекты вместо объектов своего языка.

Нам нужно использовать параметр --jvm, чтобы использовать VisualVM, т.к. Если же вы используете версию Ruby для GraalVM, то VisualVM распознает объекты Ruby. она не поддерживает родные версии Ruby.

$ ruby --jvm render.rb

Если вы создаете движок своего языка при помощи Truffle и вы делаете такие свои инструменты, как отладчик, с учетом API Truffle’а для инструментария, то каждый такой инструмент будет работать для любого языка, основанного на Truffle, и вам нужно будет написать его только один раз. Можно, если нужно, посмотреть на снимок кучи с внутренними Java объектами, как мы видели раньше, или, на закладке Summary, можно выбрать Ruby Heap и посмотреть вместо этого на реальные Ruby объекты.

Фреймворк Truffle — это что-то вроде Nexus для языков и инструментов.

С помощью Truffle и GraalVM вы можете использовать другие средства разработки, такие как отладчик в Хроме или VisualVM. Итак, пятое применение GraalVM — это платформа для создания высококачественных инструментов для языков программирования, у которых не всегда есть хороший инструментарий.

6. Дополнение JVM приложений

Новый API в пакете org.graalvm.polyglot позволяет вам загружать и запускать код, написанный на других языках и использовать значения из них. Помимо самостоятельного исполнения, а также совместного, в мультиязычных программах, поддержка других языков может быть добавлена в Java приложение.

import org.graalvm.polyglot.Context;
import org.graalvm.polyglot.Value; public class ExtendJava { public static void main(String[] args) { String language = "js"; try (Context context = Context.newBuilder().allowNativeAccess(true).build()) { for (String arg: args) { if (arg.startsWith("-")) { language = arg.substring(1); } else { Value v = context.eval(language, arg); System.out.println(v); } } } }
}

можно будет компилировать и запускать код без всяких дополнительных флагов. Если используются команды javac и java из дистрибутива GraalVM, то импорты org.graalvm.* уже будут в вашем classpath, т.е.

$ javac ExtendJava.java
$ java ExtendJava '14 + 2'
16
$ java ExtendJava -js 'Math.sqrt(14)'
3.7416573867739413
$ java ExtendJava -python '[2**n for n in range(0, 8)]'
[1, 2, 4, 8, 16, 32, 64, 128]
$ java ExtendJava -ruby '[4, 2, 3].sort'
[2, 3, 4]

Движки, используемые для выполнения команд на разных языках — те же высокопроизводительные мультиязычные версии, которые запускаются при помощи команд node или ruby, исполняемые GraalVM.

Polyglot API позволяет вам брать объекты “гостевого” языка и использовать их как Java интерфейсы, а также предоставляет более сложные способы взаимодействия. Это шестой способ использования GraalVM — как единого интерфейса для встраивания кода на других языках программирования в ваше Java приложение.

7. Дополнение платформо-зависимых приложений

Например, такой JavaScript движок, как V8, интерепретатор Python — CPython зачастую встраиваются в другие программы, т.е. В GraalVM уже включена одна библиотека, собранная способом, указанным в заголовке — это библиотека, которая позволяет вам запускать код на любом языке, поддерживаемом GraalVM, из платформо-зависимых приложений. GraalVM позволяет использовать любой язык во встроенном контексте таким же способом — при помощи связывания со своей Polyglot библиотекой. они могут быть слинкованы с другим приложением в виде библиотеки.

Вы можете пересобрать Polyglot библиотеку, чтобы включить другие языки, используя команду: Когда вы устанавливаете GraalVM, то эта библиотека там собрана и присутствует, но по умолчанию она включает только встроенную поддержку JavaScript.

$ graalvm-1.0.0-rc1/Contents/Home/jre/lib/svm/bin/rebuild-images libpolyglot

Мы собираемся сделать эквивалент нашей программы ExtendJava из предыдущего раздела, но с использованием С, как основного языка. Создадим простую программу на C, которая выполняет команды, написанные на любом языке, поддерживаемом GraalVM, которые переданы в командной строке.

Код на С

#include <stdlib.h>
#include <stdio.h> #include <polyglot_api.h> int main(int argc, char **argv) { graal_isolate_t *isolate = NULL; graal_isolatethread_t *thread = NULL; if (graal_create_isolate(NULL, &isolate) != 0 || (thread = graal_current_thread(isolate)) == NULL) { fprintf(stderr, "initialization error\n"); return 1; } poly_context context = NULL; if (poly_create_context(thread, NULL, 0, &context) != poly_ok) { fprintf(stderr, "initialization error\n"); return 1; } char* language = "js"; for (int n = 1; n < argc; n++) { if (argv[n][0] == '-') { language = &argv[n][1]; } else { poly_value result = NULL; if (poly_context_eval(thread, context, language, "unicalc", argv[n], &result) != poly_ok) { fprintf(stderr, "eval error\n"); return 1; } char buffer[1024]; size_t length; if (poly_value_to_string_utf8(thread, result, buffer, sizeof(buffer), &length) != poly_ok) { fprintf(stderr, "to string error\n"); return 1; } buffer[length] = '\0'; printf("%s\n", buffer); poly_destroy_handle(thread, result); } } return 0;
}

И опять, заметьте, нам не нужна JVM. Теперь скомпилируем и запустим этот код, используя системный компилятор С и прилинкуем polyglot библиотеку из GraalVM.

$ clang -Igraalvm-1.0.0-rc1/Contents/Home/jre/lib/polyglot / -rpath graalvm-1.0.0-rc1/Contents/Home / -Lgraalvm-1.0.0-rc1/Contents/Home/jre/lib/polyglot / -lpolyglot extendc.c -o extendc
$ otool -L extendc
extendc: .../libpolyglot.dylib ... .../libSystem.B.dylib ...

$ ./extendc '14 + 2'
16
$ ./extendc -js 'Math.sqrt(14)'
3.7416573867739413
$ ./extendc -python '[2**n for n in range(0, 8)]'
[1, 2, 4, 8, 16, 32, 64, 128]

Это седьмая вещь, которую можно сделать при помощи GraalVM — использовать одну-единственную библиотеку в вашем приложении, чтобы внедрить любой язык, поддерживаемый GraalVM.

8. Код на Java как платформо-зависимая библиотека

Если вы захотите использовать Java библиотеку из платформо-зависимого приложения, то вы можете встроить JVM в это приложение, но это довольно быстро сделает его большим и сложным. У Java великолепная экосистема с большим количеством очень высококачественных библиотек, которые, зачастую, недоступны для других экосистем, включая платформо-зависимые приложения, а также другие управляемые языки программирования.

Как и в случае с компиляцией, который был рассмотрен ранее, скомпилированные таким образом Java библиотеки не требуют JVM. GraalVM позволяет вам взять Java библиотеку, готовую или самописную, и скомпилировать ее в отдельную платформо-зависимую библиотеку, чтобы потом переиспользовать ее в других программах для этой платформы.

Я использовал SIS 0. Я написал приложение, которое использует отличную библиотеку Apache SIS для работы с геоданными, чтобы рассчитать кратчайшее расстояние (ортодромию) между двумя точками на Земле. 8, которую я закачал отдельно с http://sis.apache.org/ и вытащил из нее jar.

import org.apache.sis.distance.DistanceUtils; public class Distance { public static void main(String[] args) { final double aLat = Double.parseDouble(args[0]); final double aLong = Double.parseDouble(args[1]); final double bLat = Double.parseDouble(args[2]); final double bLong = Double.parseDouble(args[3]); System.out.printf("%.2f km%n", DistanceUtils.getHaversineDistance(aLat, aLong, bLat, bLong)); } public static double distance(IsolateThread thread, double aLat, double aLong, double bLat, double bLong) { return DistanceUtils.getHaversineDistance(aLat, aLong, bLat, bLong); } }

Скомпилируем это обычным образом и используем, чтобы получить расстояние между Лондоном и Нью-Йорком

$ javac -cp sis.jar -parameters Distance.java
$ java -cp sis.jar:. Distance 51.507222 -0.1275 40.7127 -74.0059
5570.25 km

Мы можем скомпилировать это в исполняемый файл, как мы делали для программы topten.

$ native-image --no-server -cp sis.jar:. Distance
...
$ ./distance 51.507222 -0.1275 40.7127 -74.0059
5570.25 km

Чтобы это сделать, мы объявим один или несколько методов как @CEntryPoint А можем собрать это как библиотеку, а не исполняемый файл.

...
import org.graalvm.nativeimage.IsolateThread;
import org.graalvm.nativeimage.c.function.CEntryPoint; public class Distance { ... @CEntryPoint(name = "distance") public static double distance(IsolateThread thread, double a_lat, double a_long, double b_lat, double b_long) { return DistanceUtils.getHaversineDistance(a_lat, a_long, b_lat, b_long); } ... }

Cкомпилируем код в разделяемую библиотеку и автоматически сгенерированный файл заголовка. Нам не нужно менять команду javac, потому что GraalVM автоматически кладет нужные API в classpath.

$ native-image --no-server -cp sis.jar:. -H:Kind=SHARED_LIBRARY \ -H:Name=libdistance
$ otool -L libdistance.dylib # .so on Linux
libdistance.dylib: .../libdistance.dylib ... .../CoreFoundation.framework ... .../libz.1.dylib ... .../libSystem.B.dylib ...
$ du -h libdistance.dylib
4.8M libdistance.dylib

Чтобы воспользоваться интерфейсом нашей библиотеки, нужно провести небольшую церемонию: так как VM нужно управлять кучей, потоками сборщиком мусора и другими сервисами, нужно создать экземпляр системы и сказать об этом нашему основному потоку. Теперь напишем маленькую программу на С, которая будет использовать эту библиотеку.

#include <stdlib.h>
#include <stdio.h> #include <libdistance.h> int main(int argc, char **argv) { graal_isolate_t *isolate = NULL; graal_isolatethread_t *thread = NULL; if (graal_create_isolate(NULL, &isolate) != 0 || (thread = graal_current_thread(isolate)) == NULL) { fprintf(stderr, "initialization error\n"); return 1; } double a_lat = strtod(argv[1], NULL); double a_long = strtod(argv[2], NULL); double b_lat = strtod(argv[3], NULL); double b_long = strtod(argv[4], NULL); printf("%.2f km\n", distance(thread, a_lat, a_long, b_lat, b_long)); return 0;
}

Компилируем это с использованием стандартных системных инструментов, и можем запускать наш исполняемый файл (нужно установить LD_LIBRARY_PARTH=. на Linux)

$ clang -I. -L. -ldistance distance.c -o distance
$ otool -L distance
distance: .../libdistance.dylib ... .../libSystem.B.dylib ...
$ ./distance 51.507222 -0.1275 40.7127 -74.0059
5570.25 km

Это восьмой способ применения GraalVM — компилировать код java в платформо-зависимую библиотеку, которую потом можно использовать в приложениях без использования JVM

9. Поддержка нескольких языков программирования в базе данных

Мы использовали библиотеку для создания Oracle Database Multilingual Engine (MLE), который включает в себя поддержку исполнения языков на GraalVM и модулей SQL. Одно из приложений для Polyglot библиотеки — это использование внутри БД Oracle.

Если у нас есть какая-то логика для того же приложения в БД, то она наверняка написана на SQL или PL/SQL. Например, у вас есть front-end, уже написанный на JavaScript и мы делаем валидацию email адресов, используя JavaScript модуль validator. А нам бы хотелось использовать ровно тот же валидатор, чтобы гарантировать консистентность результатов.

Загрузим MLE как Docker образ отсюда:

2. https://oracle.github.io/oracle-db-mle/releases/0. 7/docker/

Потом загрузим образ в Docker Daemon.

$ docker load --input mle-docker-0.2.7.tar.gz

Запустите образ, используя Docker, а затем, когда загрузка закончится (она может занять несколько минут), запустите Bash терминал внутри образа.

$ docker run mle-docker-0.2.7
$ docker ps
$ docker exec -ti <container_id> bash -li

Если сможем запустить sqlplus (интерактивный SQL инструмент), в терминале Bash, чтобы присоединиться к базе данных, то это значит, что база запущена и работает.

$ sqlplus scott/tiger@localhost:1521/ORCLCDB

Теперь, в терминале Bash в Docker, мы установим модуль валидатора и затем запустим команду dbjs, чтобы загрузить этот модуль в базу данных. Если все прошло успешно, выйдите из в sqlplus. Затем запустим sqlplus снова.

$ npm install validator
$ npm install @types/validator
$ dbjs deploy -u scott -p tiger -c localhost:1521/ORCLCDB validator
$ sqlplus scott/tiger@localhost:1521/ORCLCDB

После установки мы можем использовать функции из модуля validator как часть SQL выражения.

SQL> select validator.isEmail('hello.world@oracle.com') from dual;
VALIDATOR.ISEMAIL('HELLO.WORLD@ORACLE.COM')
------------------------------------------- 1
SQL> select validator.isEmail('hello.world') from dual;
VALIDATOR.ISEMAIL('HELLO.WORLD')
-------------------------------- 0

Таким образом, можно использовать логику из front-end или back-end, но внутри базы данных, вместо того, чтобы всегда вытаскивать данные для обработки из базы на сервер приложений. И это девятая вещь, которую можно делать с GraalVM — запускать языки, поддерживаемые GraalVM, внутри БД Oracle.

10. Создание языков программирования для GraalVM

Oracle Labs и наши коллеги из академической среды смогли создать новые высокопроизводительные реализации JavaScript, R, Ruby, Python и C относительно небольшой командой, потому что мы разработали фреймворк Truffle, который облегчает процесс создания языков для GraalVM.

Интерпретатор AST — возможно, самый простой способ реализации языка, потому что он работает напрямую с выходными данными парсера и не включает интерпретацию байткода или компиляцию, но этот подход, зачастую, довольно медленный. Truffle — это java библиотека, которая помогает написать интерпретатор абстрактного синтаксического дерева (AST). Мы скомбинировали это с техникой, которая называется частичным вычислением, которая позволяет Truffle использовать Graal для автоматической JIT компиляции языка, основываясь только на выходных данных вашего AST интерпретатора.

Мы много говорим о деталях Truffle и Graal в нашем проекте, но мы часто забываем упомянуть, что Truffle — это действительно простой путь реализации языка программирования для GraalVM. Вы можете использовать Truffle, чтобы создать для GraalVM свой собственный язык программирования, или высокопроизводительный вариант существующего языка программирования, или DSL. Любой, кто только что закончил начальный курс по языкам программирования, будет иметь необходимый набор навыков для этой работы. И после создания у вас будут такие вещи, как, например, отладчик, автоматически. Oracle labs сделал базовую версию Ruby быстрее, чем все предыдущие проекты, силами всего лишь одного практиканта всего за несколько месяцев.

Для того, чтобы понять, как это работает, можете, например, посмотреть реализацию оператора if. У нас здесь не так много места, чтобы полностью показать создание языка, даже самого микроскопического, но SimpleLanguage — это учебник по созданию собственного языка при помощи Truffle, основанный на простом языке в стиле JavaScript.

В примере для Lisp есть учебник, который тоже можно изучить и выполнить упражнения из него. Другие языки, написанные с использованием Truffle людьми вне Oracle Labs, включают вариант SmallTalk, Newspeak и Lisp.

Заключение

Это позволяет вам выбрать язык и модули, которые нужны, вне зависимости от среды, в которой программа выполняется или языка, который вы сейчас используете. GraalVM включает в себя большое разнообразие новой функциональности — это платформа, на базе которой можно строить языки программирования и инструменты, и использовать их в большем количестве окружений.

Там есть ссылки на загрузки и документацию и больше примеров, подобных тем, которые были показаны в этой статье. Чтобы попробовать GraalVM, сходите на http://www.graalvm.org/.

Дайте нам знать о том, как прошли эксперименты с GraalVM в вашем приложении и пришлите нам отзыв на @ChrisGSeaton или @shelajev. Попробуйте сделать те примеры, о которых мы говорили здесь и попробуйте с ними поиграть с ними и посмотреть, что ещё можно сделать.

Благодарности: Oleg Šelajev, Olya Gupalo и Doug Simon

Теги
Показать больше

Похожие статьи

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *

Кнопка «Наверх»
Закрыть