Статьи

Project Loom: Java с более сильным волокном

Популярность приходит по цене. Ява является и была очень популярным языком, вызывающим как похвалу, так и критику. Хотя было бы справедливо ожидать спада после стольких лет и такого большого наследия, Java на самом деле находится в довольно хорошей форме и имеет сильную техническую дорожную карту. Наступает новая эра Java, и через несколько лет в JVM-стране все может измениться. В OpenJDK есть несколько технически впечатляющих проектов, которые мы надеемся использовать в ближайшее время, и которые могут повлиять не только на Java, но и на другие языки.

Помимо Loom , основной темы этой статьи, вы также должны следить за Valhalla , которая в некоторых случаях может удвоить производительность Java, и за Graal , который делает так много вещей, что я даже не знаю, с чего начать! И, конечно, язык становится менее многословным, благодаря Амбер .

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

Одним из примеров этого является Loom + Graal, который продолжает (сопрограммы) и заблаговременно компилирует, что делает Go менее привлекательным, чем сейчас.

Loom + Amber дает вам волокна (позволяющие потенциально более простые системы актеров) и более короткий синтаксис, также делая Scala менее привлекательным, чем сейчас. Valhalla + Graal может уменьшить разрыв в производительности с C ++. И только Graal может подтолкнуть Python к запуску в JVM, или, по крайней мере, PySpark может извлечь из этого большую пользу.

Но давайте сосредоточимся на Loom. И так как в настоящее время нет много практической информации об этом, мы дополнительно объясним, построим и используем эту экспериментальную JVM для выполнения некоторых тестов. Пусть числа говорят!

Project Loom

В Java раньше были зеленые потоки, по крайней мере, в Solaris, но современные версии Java используют так называемые собственные потоки . Нативные потоки хороши, но относительно тяжелы, и вам может понадобиться настроить ОС, если вы хотите иметь десятки тысяч из них.

Project Loom представляет продолжения (сопрограммы) и волокна (тип зеленых нитей), позволяющие выбирать между нитями и волокнами. С Loom даже ноутбук может легко управлять миллионами волокон, открывая двери для новых или не очень новых парадигм.

Небольшое отступление: Эрланг

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

В Erlang программы обычно имеют много долгоживущих, не очень загруженных потоков. На самом деле ожидается, что он будет обслуживать каждого пользователя выделенным потоком. Многие из этих потоков могут выполнять сетевые операции (в конце концов, Erlang был разработан Ericsson для телекоммуникационной отрасли), и эти сетевые операции являются синхронными. Да, синхронно. Мы могли бы обслуживать миллион пользователей с одной машиной с большим количеством оперативной памяти, используя простые синхронные сетевые операции.

Синхронный Vs. Асинхронный

В течение многих лет нам говорили, что масштабируемые серверы требуют асинхронных операций, но это не совсем так.

Конечно, если вам нужно масштабировать с использованием пула потоков (или даже одного отдельного потока), у вас в основном нет альтернативы: вы должны использовать асинхронные операции. И асинхронные операции могут очень хорошо масштабироваться.

Когда я присоединился к Opera Software в 2008 году, я был немного удивлен, узнав, что Presto, ядро ​​браузера, является однопоточным. Да, одна тема. Но этого было достаточно. Десятки вкладок для рендеринга HTML и обработки JavaScript, сетевых загрузок, файловых операций, файлов cookie, кеша — вы называете это. И только один поток, множество асинхронных операций и обратных вызовов везде. И это сработало довольно хорошо.

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

Не было бы неплохо получить простоту синхронных операций с производительностью асинхронных вызовов?

Волокна на помощь

Ткацкий станок вводит волокна. Это здорово, но этого недостаточно. Чтобы делать полезные вещи, вам нужен сетевой стек, дружественный к волокну Когда я попробовал Loom несколько месяцев назад, это был не тот случай. Создание около 40-50 волокон было достаточно, чтобы начать иметь сетевые ошибки. Проект был слишком незрелым.

В июне JDK 13 принял в основной строке JEP 353 ( https://openjdk.java.net/jeps/353 ), который переписал Java Socket API, чтобы сделать его более дружественным к волокну.

Хотя не все работает, Loom теперь можно использовать для сетевых операций.

Пришло время иметь систему актеров, которая может использовать волокна Loom.

Хорошо, может быть, это немного рано, так как Project Loom все еще экспериментален, а JDK 13 выйдет в сентябре, но я не удержался. Поэтому я создал и открыл небольшую систему актеров, способную использовать Loom: Fibry . Мы будем использовать его для сравнения Loom и определения, действительно ли волокна лучше, чем нити.

Актеры и Фибри

Актеры используются в многопоточной среде для достижения параллелизма относительно простым способом. В частности, субъекты являются однопоточными, поэтому у вас нет проблемы параллелизма по определению, если они действуют только в своем состоянии; Вы можете изменить состояние актера, отправляющего ему сообщения.

Erlang обеспечивает эту безопасность, имея только константы (без циклов for, и вы не можете даже переключить две переменные традиционным способом …), Java — нет. Но актеры все еще могут быть очень полезны.

Отличный вариант использования для актеров — это когда у вас есть долгосрочная задача, которая особенно легка, обычно потому, что она зависит от сетевых операций и просто ждет, когда клиенты что-то предпримут. Например, сеть IoT может иметь все устройства, постоянно подключенные к управляющему серверу, отправляя сообщения только время от времени. Чат является еще одним примером программы, которая может принести пользу актерам. И сервер, поддерживающий WebSockets, может быть другим кандидатом.

Fibry — это моя система Actor System, разработанная для того, чтобы быть маленькой, гибкой, простой в использовании и, конечно, использовать преимущества Loom. Он работает с любой версией Java, начиная с Java 8 и далее, и не имеет никаких зависимостей, кроме как требует, чтобы Loom использовал волокна.

Строительный ткацкий станок

Сборка ткацкого станка занимает немного времени, но достаточно проста. Вы можете получить некоторую основную информацию здесь .

После установки Mercurial (OpenJDK все еще работает на Mercurial), вам нужно выполнить следующие команды:

hg clone http://hg.openjdk.java.net/loom/loom 
cd loom 
hg update -r fibers
sh configure  
make images

Возможно, вам потребуется установить некоторые пакеты во время этого процесса, но команда sh configure должна указать, какие команды нужно запустить.

Это оно!

Теперь вы можете создать «Hello World» с волокнами:

var fiber =
  FiberScope.background().schedule(() -> System.out.println("Hello World"));

Вы можете получить больше информации здесь .

Мы не собираемся использовать волокна напрямую, но мы будем использовать Fibry, так как нас в первую очередь интересует, как актеры могут извлечь из них пользу.

Сравнение волокон и нитей

Давайте посчитаем, сколько времени нам нужно для создания (и поддержки) потоков 3K. Вы можете попробовать большее число, если ваша ОС правильно настроена. Я использую стандартную конфигурацию виртуальной машины c5.2xlarge с Loom JDK без параметров. Он может быть создан с потоками 3K, но не 4K.

Когда вы запустите этот тест со многими потоками, будьте готовы; это может быть немного сложно на вашем компьютере, и вам может потребоваться перезагрузка.

for(int i=0; i<3000; i++)
  Stereotypes.threads().sink(null);

Этот код создает 3K «потоки стока», которые просто отбрасывают полученные сообщения. В моей виртуальной машине требуется 210 мсек.

Давайте попробуем создать волокна 1M, используя  fibers() метод вместо threads():

for(int i=0; i<1_000_000; i++)
  Stereotypes.fibers().sink(null);

На моей виртуальной машине я могу создавать волокна 3M. 3 миллиона

С Loom мы можем создать в 1000 раз больше волокон, чем нитей ! Вы можете точно настроить ВМ и ОС для увеличения количества потоков, но, насколько я понимаю, ограничение составляет около 32 КБ.

Волокна также гораздо быстрее создавать. 3K-потокам требуется 210 мс, но за то же время можно создать 200K-волокна, что означает, что создание волокон примерно в 70 раз быстрее, чем создание потоков! 

Измерение переключения контекста

Как правило, компьютер должен переключаться с одного потока на другой, и это занимает небольшое, но значительное время. Теперь мы попытаемся увидеть, быстрее ли волокна в этой конкретной проблеме. Чтобы попытаться измерить переключение контекста, мы создадим два потока и будем синхронно обмениваться сообщениями с кодом, похожим на этот (вам нужно позвонить,   ActorSystem.setDefaultStrategy() чтобы выбрать потоки или волокна):

var actorAnswer = ActorSystem.anonymous().newActorWithReturn((Integer n) -> n * n);

Stereotypes.def().runOnceSilent(() -> {
  for (int i = 0; i < 250_000; i++)
    actorAnswer.sendMessageReturn(i).get();
}).closeOnExit(actorAnswer).waitForExit();

Здесь мы  actorAnswer можем вернуть квадрат числа, и другой актер попросит сделать это 250 тысяч раз, ожидая результата.

Стратегия определяет, использует ли она нити или волокна.

На моей виртуальной машине для выполнения этой задачи потокам требуется около 4700 мс, а для волокон — около 1500 мс, поэтому волокна могут обмениваться синхронными сообщениями в три раза больше, чем потоки .

Сетевые операции

Давайте теперь проверим, в порядке ли сетевые операции.

Ниже приведен простой HTTP-код HelloWorld, который запускает встроенный Java-HTTP-сервер:

Stereotypes.def().embeddedHttpServer(12345, exchange -> "Hello World!");

Каждый раз, когда подключается новый клиент, для обработки запроса создается новый субъект. В этом случае потоки и волокна работают очень схожим образом — примерно 2200 запросов в секунду. Здесь узким местом является, вероятно, встроенный HTTP-сервер, который не предназначен для загрузки сервера.

Итак, давайте попробуем написать супер простой HTTP-сервер, который всегда отвечает одной и той же строкой:

Stereotypes.def().tcpAcceptorSilent(12345, conn -> {
  try (var is = conn.getInputStream(); var os = conn.getOutputStream()) {
    // Skips till the end of the HTTP request
    while (is.read() != '\n' || is.read() != '\r' || is.read() != '\n') { }

    os.write("HTTP/1.1 200 OK\r\nContent-Length: 6\r\n\r\nHello!".getBytes());
  }
}, null).waitForExit();

Я тестирую с Apache Bench, используя 100 потоков:

ab -k -n 50000 -c 100 http://localhost:12345/

Потоковая версия может обслуживать почти 11 000 запросов в секунду, а количество волокон превышает 24 000. Итак, в этом тесте  волокна в два раза быстрее нитей .

Волокна всегда быстрее?

Не совсем. По какой-то причине потоки, кажется, немного быстрее отправляют асинхронные сообщения со скоростью около 8,5 Мбит / с, в то время как волокна достигают максимума около 7,5 Мбит / с. Кроме того, потоки, по-видимому, меньше страдают от перегрузки при увеличении количества потоков, в этом конкретном тесте.

Это может быть решаемым переключением на другую систему обмена сообщениями, чем та, которую использует Fibry. Кроме того, давайте не будем забывать, что Loom еще не готов к производству, поэтому есть еще возможности для улучшения поведения.

Если вы хотите выполнить некоторые тесты самостоятельно, вы можете найти полный код и другие тесты здесь: https://github.com/lucav76/Fibry Bench /

Выводы

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

Я с нетерпением жду появления Loom в основной линии OpenJDK. Ты?