LINUX.ORG.RU

Опыт объединения Java, JavaScript, Python и Ruby с использованием GraalVM

 graalvm, , , ,


0

4

В ноябре этого года вышел более-менее стабильный LTS-релиз GraalVM 20.3.0 от Oracle и я решил с ним поэкспериментировать. Для тех кто не в курсе, GraalVM позволяет использовать в едином окружении различные популярные языки программирования и обеспечивает их разностороннее взаимодействие в рамках некоторой общей среды выполнения. Платформа GraalVM вместе с исполняемой программой на смеси самых разных языков может быть представлена в виде автономного и самодостаточного исполняемого файла, либо работать поверх OpenJDK, Node.js или даже внутри Oracle Database. Наглядная схема из официальной документации:

https://www.graalvm.org/docs/img/graalvm_architecture.png

Поддержка гостевых языков осуществляется с помощью фреймворка Truffle, на основе этой библиотеки можно даже реализовать собственный язык программирования, который получит все плюшки платформы, вроде JIT-компиляции, многостороннего взаимодействия и прочего полезного. Из коробки в дистрибутиве GraalVM сразу присутствует возможность использования:

  • Java, Kotlin, Scala и других языков JVM-платформы.
  • JavaScript вкупе с Node.js и сопутствующим инструментарием.
  • C, C++, Rust и других языков, которые могут быть скомпилированы в LLVM bitcode.

Экспериментальная поддержка заявлена для Python, Ruby, R и WebAssembly.

Собственно, хватит лирики, вот простенький прототип, использующий из экосистем JavaScript, Python и Ruby батарейки, реализующие server-side подсветку синтаксиса фрагментов исходного кода. Подобные библиотеки с богатым охватом языков программирования, которые они могут подсвечивать, отсутствуют на JVM-платформе:

// Highlighter.java, no comments, no checks.
// $ javac Highlighter.java
// $ jar -cvfe highlighter.jar Highlighter *.class
// $ cat hello.py | java -jar highlighter.jar rouge python
import org.graalvm.polyglot.Context;

import java.io.File;
import java.io.FileNotFoundException;

import java.util.Scanner;

public class Highlighter {
  private abstract class Highlight {
    protected final Context polyglot =
      Context.newBuilder("js", "python", "ruby").allowAllAccess(true).allowIO(true)
        .build();

    protected abstract String language();
    protected abstract String renderHtml(String language, String rawCode);

    protected String execute(String sourceCode) {
      try {
        return polyglot.eval(language(), sourceCode).asString();
      } catch (RuntimeException re) { re.printStackTrace(); }
      return sourceCode;
    }

    protected void importValue(String name, String value) {
      try {
        polyglot.getBindings(language()).putMember(name, value);
      } catch (RuntimeException re) { re.printStackTrace(); }
    }
  }

  private class Hjs extends Highlight {
    @Override
    protected String language() { return "js"; }

    @Override
    public String renderHtml(String language, String rawCode) {
      importValue("source", rawCode);

      String hjs = "";
      try {
        hjs = new Scanner(new File("highlight.min.js")).useDelimiter("\\A").next();
      } catch (FileNotFoundException fnfe) { fnfe.printStackTrace(); }

      final String renderLanguageSnippet =
        hjs + "\n" +
        "hljs.highlight('" + language + "', String(source)).value";
      return execute(renderLanguageSnippet);
    }
  }

  private class Rouge extends Highlight {
    @Override
    protected String language() { return "ruby"; }

    @Override
    public String renderHtml(String language, String rawCode) {
      importValue("$source", rawCode);
      final String renderLanguageSnippet =
        "require 'rouge'" + "\n" +

        "formatter = Rouge::Formatters::HTML.new" + "\n" +
        "lexer = Rouge::Lexer::find('" + language + "')" + "\n" +
        "formatter.format(lexer.lex($source.to_str))";
      return execute(renderLanguageSnippet);
    }
  }

  private class Pygments extends Highlight {
    @Override
    protected String language() { return "python"; }

    @Override
    public String renderHtml(String language, String rawCode) {
      importValue("source", rawCode);
      final String renderLanguageSnippet =
        "import site" + "\n" +
        "from pygments import highlight" + "\n" +
        "from pygments.lexers import get_lexer_by_name" + "\n" +
        "from pygments.formatters import HtmlFormatter" + "\n" +

        "formatter = HtmlFormatter(nowrap=True)" + "\n" +
        "lexer = get_lexer_by_name('" + language + "')" + "\n" +
        "highlight(source, lexer, formatter)";
      return execute(renderLanguageSnippet);
    }
  }

  public String highlight(String library, String language, String code) {
    switch (library) {
      default:
      case "hjs": return new Hjs().renderHtml(language, code);
      case "rouge": return new Rouge().renderHtml(language, code);
      case "pygments": return new Pygments().renderHtml(language, code);
    }
  }

  public static void main(String[] args) {
    Scanner scanner = new Scanner(System.in).useDelimiter("\\A");
    if (scanner.hasNext()) {
      String code = scanner.next();
      if (!code.isEmpty()) {
        System.out.println(new Highlighter().highlight(args[0], args[1], code));
      }
    }
  }
}

Как видно, тут смешаны сразу четыре разных языка программирования. Утилита принимает на вход stdin в виде текста исходного файла, передаваемые аргументы определяют используемую библиотеку для подсветки и язык фрагмента, затем подсвечивается код и выводится готовый HTML на stdout терминала.

Подсветка фрагментов кода на стороне сервера лишь простейший пример использования библиотек, которые недоступны для определённой платформы, но доступны на нескольких других. С тем же успехом можно рассматривать какую-нибудь гораздо более полезную научную батарейку, написанную на Python или R, что-нибудь из ML и т. д. Получается, что JVM-платформу со своей кучей библиотек мы можем обогатить батарейками из экосистем других языков программирования и использовать их эксклюзивные библиотеки, альтернативы которых просто недоступны на нашей платформе.

Рецепт установки GraalVM, поддержки языков и библиотек, компиляция и запуск прототипа:

curl -LOJ https://github.com/graalvm/graalvm-ce-builds/releases/download/vm-20.3.0/graalvm-ce-java8-linux-amd64-20.3.0.tar.gz
# curl -LOJ https://github.com/graalvm/graalvm-ce-builds/releases/download/vm-20.3.0/graalvm-ce-java11-linux-amd64-20.3.0.tar.gz
cd /opt/
sudo mkdir graalvm
sudo chown `whoami`:`whoami` graalvm
cd /opt/graalvm/
tar -xvzf ~/graalvm-ce-java8-linux-amd64-20.3.0.tar.gz
rm ~/graalvm-ce-java8-linux-amd64-20.3.0.tar.gz

export GRAALVM_HOME=/opt/graalvm/graalvm-ce-java8-20.3.0
export JAVA_HOME=$GRAALVM_HOME
export PATH=$GRAALVM_HOME/bin:$PATH

gu install python
gu install ruby
# /opt/graalvm/graalvm-ce-java8-20.3.0/jre/languages/ruby/lib/truffle/post_install_hook.sh

graalpython -m ginstall install setuptools
curl -LOJ https://github.com/pygments/pygments/archive/2.7.3.tar.gz
tar -xvzf pygments-2.7.3.tar.gz
cd pygments-2.7.3/
graalpython setup.py install --user
cd ..
rm -Rf pygments-2.7.3/ pygments-2.7.3.tar.gz

gem install rouge

curl -LOJ https://cdnjs.cloudflare.com/ajax/libs/highlight.js/10.4.1/highlight.min.js

javac Highlighter.java
jar -cvfe highlighter.jar Highlighter *.class

cat hello.py
#!/usr/bin/env python
print("Hello, World!")

cat hello.py | java -jar highlighter.jar hjs python
<span class="hljs-comment">#!/usr/bin/env python</span>
print(<span class="hljs-string">"Hello, World!"</span>)

cat hello.py | java -jar highlighter.jar rouge python
<span class="c1">#!/usr/bin/env python
</span><span class="k">print</span><span class="p">(</span><span class="s">"Hello, World!"</span><span class="p">)</span>

cat hello.py | java -jar highlighter.jar pygments python
<span class="ch">#!/usr/bin/env python</span>
<span class="nb">print</span><span class="p">(</span><span class="s2">"Hello, World!"</span><span class="p">)</span>

Благодаря поддержке AOT-компиляции в GraalVM можно даже собрать автономный нативный исполняемый файл из JAR-пакета:

sudo yum install gcc glibc-devel zlib-devel libstdc++-static

gu install native-image
# gu rebuild-images polyglot libpolyglot

native-image --language:js -jar highlighter.jar

cat Highlighter.java | ./highlighter hjs java

Вместо Java VM там будет использована легковесная и низкоуровневая Substrate VM, но с преимуществами JIT-компилятора в случае AOT придётся попрощаться. Зато запуск просто молниеносный, как у всех нативных программ. Стоит отметить, что пока у меня удалось сформировать подобный исполняемый файл лишь для связки JavaScript + Java. Создание подобных нативных образов довольно продолжительная и ресурсоёмкая операция, особенно по памяти. Для сборки примера потребовалось где-то 6 GB RAM, а в более сложных случаях требуется и целых 20 GB RAM.

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

Все исходники и рецепты я выложил на GitHub: https://github.com/EXL/CodePolyglot
На Хабре имеется скучная и длинная статья про мои изыскания, может кому-нибудь будет интересно её почитать: https://habr.com/ru/post/534044/
Потыкать палочкой прототип в виде сайтика на GraalVM и Spring Boot можно тут: https://code.exlmoto.ru/
Примечание: не факт, что я долго буду держать сайт в онлайне, так что не рассчитывайте сохранять там что-то ценное.

Мне интересно ближайшее будущее GraalVM, похоже Oracle настроен очень серьёзно. Пока проект позиционируется им как альтернативная и идеальная платформа для микросервисов, но уже сейчас его разработка имеет влияние и на классический OpenJDK, например, в релизе JDK 15 была дропнута поддержка JavaScript-движка Nashorn, а в качестве его замены Oracle предлагает попробовать именно GraalVM. Кто знает, вдруг GraalVM в будущем будет предлагаться в качестве рекомендуемой JVM-платформы по умолчанию вместо OpenJDK? Время нам покажет.

Предлагаю обсудить эту грандиозную затею Oracle, пригодится ли кому-нибудь использовать все эти фичи GraalVM на практике? На официальном сайте платформы есть интересное заявление о том, что наша отечественная социальная сеть «Одноклассники» уже использует GraalVM в продакшене для server-side рендеринга React.js, что позволяет добиться хорошей отзывчивости на медленных интернет соединениях.

P.S. Поздравляю с наступающим Новым Годом анонимных и зарегестрированных пользователей ЛОРа. Счастья и крепкого сибирского здоровья вам, ребята!

★★★★★

Последнее исправление: EXL (всего исправлений: 4)

Ответ на: комментарий от drsm

Вот для случая Enterprise Level задач типа: «третьего дня завезли новые фичи. от правой подситемы приходит джсон строкой, от левой пачка csv, + у нас тут еще есть мега наша Объектная Модель. надо вывалить все это в отчет. должно быть готово вчера, послезавтра это будет никому не нужно. послезавтра будет другое говно.», ты выберешь Java ?

Конечно. Готово будет, когда я раздуплюсь через неделю и ещё две-три недели будет тестироваться. Wait, у меня же ещё отпуск! Это – Enterprise, детка. Вот поэтому и Java.

anonymous
()
Ответ на: комментарий от rukez

Почему? Потому что это будет работать и это будет легко и понятно.

ну OK, YMMV.

Охраняет, например, Шереметьево, так что довольно таки энтерпрайзненько :-)

респект. тоже там бывал со своим софтом 3-5 лет назад, закрыли проект, какраз Java + Js SPA :)

drsm ★★
()
Ответ на: комментарий от EXL

Меня смущает больше то, что с таким неудобным интерфейсом самого языка им вообще кто-то пользуется.

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

ну Java 15 язык не поворачивается уже назвать неудобной

https://www.marcobehler.com/guides/a-guide-to-java-versions-and-features

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

drsm ★★
()
Ответ на: комментарий от drsm

ну Java 15 язык не поворачивается уже назвать неудобной

Это да, но Java пока всё ещё в «догоняющей» позиции по отношению к языкам JVM-платформы.

EXL ★★★★★
() автор топика
Ответ на: комментарий от drsm

ну Java 15 язык не поворачивается уже назвать неудобной
https://www.marcobehler.com/guides/a-guide-to-java-versions-and-features

Если суммировать коротко эти нововведения, вроде лямбд, switch, локального вывода типов, поточных контейнеров, то их можно описать коротко одной фразой: классы не нужны.

важно еще, что для явы, точнее для самого жвм рантайма, можно либы найти, вменяемого качества, с наиболее высокой кмк вероятностью, это часто решает

Ну то есть можно найти VM, которая будет предсказуемо тормозить и жрать память. Чтобы заказчик точно знал, что система не упадет на его серваке с 192 Гб оперативы.

byko3y ★★★★
()
Ответ на: комментарий от byko3y

то их можно описать коротко одной фразой: классы не нужны.

Вообще занятно посмотреть во что превратился, например, Spring WebFlux, который подвязан на подобный стиль программирования:

Flux.fromIterable(participants)
    .flatMap { participant -> getPerson(participant).map { PersonInfo(participant, it) } }
    .collectList()
    .flatMapMany { recipients ->
        val borrower = recipients
            .filter { it.participant.role == ParticipantRole.BORROWER }
            .map { it.person }
            .firstOrNull() ?: throw BusinessException(SystemError.BORROWER_NOT_FOUND)

        recipients
            .filter { if (currentParticipant == null) true else currentParticipant.casId == it.participant.casId }
            .distinctBy { it.person.confirmedPhone }
            .toFlux()
            .flatMap { recipient ->
                val template = templateMapping.getValue(recipient.participant.role)
                createNotification(dealId, message, borrower, template, recipient.person)
            }
    }
    .flatMap { notification ->
        logger.info { "Sending sms $notification to ${notification.recipient.casId}" }
        notificationClient.send(notification).map {
            notification
        }
    }
    .onErrorContinue { exception, obj ->
        handleException(obj, message, exception)
    }
    .onErrorResume {
        handleException(null, message, it)
        Flux.empty()
    }
EXL ★★★★★
() автор топика
Ответ на: комментарий от EXL

Вообще занятно посмотреть во что превратился, например, Spring WebFlux, который подвязан на подобный стиль программирования

А если переписать на классах, то получится три экрана.

byko3y ★★★★
()
Ответ на: комментарий от byko3y

А если переписать на классах, то получится три экрана.

Зато это можно хоть как-то дебажить. А вот подход выше выливается в:

Если вы начинаете писать проект на Spring Webflux, то будьте готовы к тому, что придется подключать к приложению дополнительный агент — Reactor Debug Agent (и в проде тоже), иначе логи будут выглядеть так:

<нечитаемая stacktrace-простыня>

Польза от такого лога сомнительна, своего кода в трассировке стека вы не увидите.

Если вы привыкли использовать АРМ и это не New Relic/Dynatrace или другие коммерческие мастодонты, то можете выкинуть его сразу — адекватной инструментации Netty и Spring Webflux практически нигде нету.

// Отсюда: https://habr.com/ru/company/domclick/blog/504304/

EXL ★★★★★
() автор топика
Ответ на: комментарий от EXL

Да, инструменты для отладки однопоточных синхронных приложений не подходят при другом подходе. В том числе они не подходят, например, для многопоточной отладки. В том же JS уже давно в браузерах есть инструменты отладки асинхронщины.

Я вот только не пойму, какого черта это называется «reactive Streams», а не «Asynchronous Streams»?

https://www.reactive-streams.org/

В чем они «reactive»? Они на что-то реагируют?

byko3y ★★★★
()
Ответ на: комментарий от byko3y

Я вот только не пойму, какого черта это называется «reactive Streams», а не «Asynchronous Streams»?

В чем они «reactive»? Они на что-то реагируют?

Разве не потому, что термин «Reactive» новый модный buzzword?

EXL ★★★★★
() автор топика
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.