Функциональное будущее: Lunatic с Бернардом Колобарой

В этом месяце в эпизоде Functional Futures наш гость — Бернард Колобара — создатель Lunatic, вдохновленной Erlang среды выполнения для WASM.

В этом эпизоде мы говорим о Lunatic, WebAssembly, BEAM (виртуальная машина Erlang) и многом другом.

Вы можете посмотреть полный эпизод на нашем канале YouTube или послушать аудиоверсию на нашей странице подкаста.

Ниже вы можете прочитать некоторые из основных моментов эпизода, отредактированные для ясности.

Основные моменты из нашей беседы с Бернардом Колобарой

Что такое Lunatic?

Йонн: Я узнал о Lunatic некоторое время назад, потому что мой друг и один из первых Serokellers Ante Kegalj участвовал в вашем материале. Мир тесен. Не могли бы вы рассказать нашим зрителям, что такое Lunatic, как он развивался, и какая история стоит за его названием?

Бернард: Lunatic — это, по сути, среда выполнения WebAssembly, вдохновленная принципами Erlang. Это попытка привнести некоторые идеи из мира Erlang во все языки программирования, которые могут быть скомпилированы в WebAssembly.

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

Самая интересная часть — это компонент WebAssembly.

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

Есть и другие проблемы с async Rust. Например, если у вас есть какая-то интенсивная вычислительная часть в Rust, вы запускаете ее и не возвращаетесь к исполнителю, это приведет к тому, что поток застрянет, и отзывчивость системы пострадает. Есть также различные эргономические проблемы, потому что в Rust нельзя иметь асинхронные функции. Так что было много разных проблем.

В Erlang это кажется таким элегантным — вы пишете код линейно, без раскраски функций, без async/await, и он по-прежнему работает высокопроизводительно, я бы сказал, по расписанию с [неслышно]. Моей идеей было: «Как я могу получить такой опыт в Rust?».

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

Что мы делаем с Lunatic, так это вставляем точки вытеснения в байткод. Мы берем приложение Rust, компилируем его в WebAssembly, вставляем то, что, по сути, является счетчиком уменьшения, если вы знакомы с Erlang. После того, как приложение поработает некоторое время, оно вернется обратно. Таким образом, вы можете писать любой код, вы даже можете сделать перекрестную компиляцию, взять какую-то базу кода на языке C и соединить ее с базой кода на языке Rust, и все останется всегда работоспособным, вернется в то же время и будет работать нормально.

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

Что касается названия: в самом начале я не знал о WebAssembly, и когда мне захотелось иметь более высокопроизводительный Erlang, я начал работать над смесью между Lua, потому что я знал, что у Lua есть действительно замечательный JIT-компилятор. Я делал среду исполнения Lua с некоторыми элементами Erlang, и назвал ее Lunatic. На логотипе, если вы заметили, изображена луна. Он также вдохновлен Lua. Отсюда и пошло название. И я просто сохранил это название, но сейчас проект развивается в совершенно другом направлении, и в нем нет никаких частей Lua.

Почему WebAssembly?

Джонн: Не могли бы вы рассказать подробнее о том, что такое WebAssembly, и почему вы выбрали именно WebAssembly, а не JVM, например?

Бернард: Я могу кратко рассказать о WebAssembly для тех, кто с ней не знаком. В принципе, я думаю, что «Web» часть названия WebAssembly немного неудачна. Я имею в виду, что в настоящее время WebAssembly в основном развертывается внутри браузера, так что это имеет смысл, но WebAssembly — это, по сути, определение байткода. Это действительно простое определение байткода, которое хорошо сопоставляется с современными инструкциями процессора. Поэтому вы можете очень быстро скомпилировать его в действительно эффективный код, который смогут выполнить компьютеры.

Это противоположность JVM: у нее нет ничего другого. Например, у нее нет сборщика мусора, у нее нет никаких API, она совершенно пустая. И именно поэтому она является хорошей мишенью для таких языков, как C, C++ и Rust, потому что она не предполагает никакого времени выполнения. Он всегда предназначен для встраивания куда-то еще.

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

Мы также используем это в распределенной системе Lunatic. Вы можете создавать различные узлы, работающие под управлением различных операционных систем и различных архитектур процессоров. Так что, по сути, вы можете сказать: «Запустите эту функцию Rust, только на этой другой машине», и мы передадим модуль в фоновом режиме, скомпилируем его под родную архитектуру, а затем запустим его там.

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

Это не статический анализ кода на Rust или C, мы можем взять любой байткод WebAssembly, проанализировать его, а затем поместить туда инструкции, и это шаг во время компиляции just-in-time.

Мы используем компилятор под названием Cranelift, который позволяет генерировать эффективный машинный код из WebAssembly. Пока идет этот процесс, мы просто ставим дополнительные точки типа «теперь уступите планировщику», так что вся система остается отзывчивой, и мы получаем ту часть с низкой задержкой, которую обещает Erlang.

Я думаю, это действительно интересно, потому что Erlang дает вам низкую задержку, но если вы хотите высокой производительности, вам нужно опуститься до C. И затем у вас есть NIFs или Rustler — это тоже популярный проект — так что вы можете соединять родные модули Rust, но как только вы это делаете, вы теряете всю эту отзывчивость виртуальной машины Erlang, и тогда вы снова оказываетесь сами по себе. Вам нужно написать какой-то код, который периодически возвращается, и если вы просто возьмете библиотеку для обработки изображений, вы не сможете ее модифицировать, чтобы она не делала слишком много обработки изображений и возвращалась обратно.

И нас это не очень волнует, потому что вы можете соединить несколько проектов и сделать кодовую базу, состоящую наполовину из C, наполовину из Rust, и она будет просто работать, нас это не волнует.

Что также важно для WebAssembly, как я уже говорил, у нее нет никаких API, в принципе. Мы называем их хост-функциями — это слой между кодом, выполняющимся внутри модуля WebAssembly, и окружением хоста. Например, хорошим примером хост-среды является браузер. Поэтому вы можете предоставить некоторые функции для взаимодействия между браузером и моделью WebAssembly. И то, что мы делаем с Lunatic, — мы раскрываем функции: так вы можете порождать новые процессы, отправлять сообщения. Потому что это также не имеет смысла для многих конструкций, которые живут в языке программирования, например, в Rust, потоки. Что бы это вообще значило — породить поток в браузере — потому что большая часть выполнения однопоточна. Поэтому эта функциональность просто недоступна при компиляции в WebAssembly.

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

OTP в Lunatic

Jonn: Вы упомянули, что не хотите навязывать архитектуру и тому подобное пользователям. Мне интересно, что вы делаете с OTP и поставляете ли вы его?

Бернард: Я думаю, есть две части виртуальной машины. Во-первых, есть фундаментальная среда выполнения, которая реализована в Rust и компилируется в нативный код, а затем запускает WebAssembly. А затем есть гостевые библиотеки, которые вы используете для написания приложений, использующих открытые возможности базовой платформы. В платформе мы предоставляем минимальное количество таких вещей, как процессы, связывание, обмен сообщениями, почтовые ящики и то, что вы не можете сделать в гостевом пространстве. Но все остальное мы предоставляем, например, у нас есть супервизоры в нашей библиотеке Rust и некоторые абстракции, похожие на GenServer, так что вы можете писать GenServers и супервизоры.

Это также похоже на Erlang, потому что OTP — это библиотека, и поэтому мы предоставляем это также как библиотеку поверх него. Сейчас мы много внимания уделяем Rust, но эти абстракции действительно зависят от языка, который вы используете. В Rust мы используем преимущества системы типов, чтобы выразить, как работает наш супервизор, что такое дочерние элементы и стратегия супервизора. Это во многом зависит от системы типов Rust. Мы хотели, чтобы модель программирования была очень близка к языку.

Например, в Rust мы придерживаемся грузовых и стандартных инструментов. Так что если вы знаете Rust, вы просто меняете конфигурацию cargo, указывая, что runner — это Lunatic […] Так что если вы делаете cargo run, он просто запускается на Lunatic, компилируется напрямую, если вы делаете cargo test, все работает из коробки. Таким образом, разработчики чувствуют себя как дома.

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

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

Jonn: Но могу ли я все же скомпилировать программу WASM, которая будет посылать процессам произвольный мусор?

Бернард: Да, безусловно, можно. Мы также ожидаем, что люди иногда пишут на разных языках, так что вы пишете один процесс на C, а другой на Rust, так что сообщения, которые посылаются друг другу, являются просто буферами, потому что мы не можем делать никаких предположений. Вы можете сериализовать любые данные, которые хотите, внутри него и десериализовать на другой стороне, и мы оставляем это на усмотрение разработчика — формат сериализации и прочее — который выглядит как: «вот буфер, напишите в нем что-нибудь, а мы отправим это другому процессу и сообщим ему, что он прибыл».

Go и Lunatic

Jonn: А как насчет других языков? Например, Go. Некоторым людям нравится Golang, и я их понимаю теперь, когда в нем есть дженерики, но одна из вещей, которую они иногда упоминают, когда объясняют, почему им нравится Golang, это то, что он взаимодействует с WASM.

Насколько сложно мне, как пользователю Golang, будет использовать возможности Lunatics, функции хоста и т.д.?

Бернард: Компилятор Go — я не думаю, что он компилирует в WebAssembly, но есть проект под названием TinyGo. По сути, это Go, предназначенный для встраиваемых устройств, и в нем есть отличная поддержка WebAssembly. Я никогда не пробовал его на практике, но я знаю, что простые примеры, по крайней мере, работают. То есть я беру приложение hello world, компилирую его в WebAssembly, и поскольку он также использует WASI, системные интерфейсы WebAssembly, Lunatic понимает их, так что вы можете открывать файлы, записывать файлы, писать в system out, читать что-то из стандартного ввода. Но чего у нас нет, так это библиотеки для специфических возможностей Lunatic: порождение новых процессов, отправка сообщений. Этого не существует, поэтому кто-то должен внести свой вклад.

В настоящее время мы в основном сосредоточены на Rust, мы хотим сначала довести до ума эту историю, прежде чем расширяться дальше, но я думаю, что это вполне осуществимо. И что еще интересно, как я уже говорил, поскольку WebAssembly ничего не предполагает, и это такой простой байткод, после компиляции кода Go в WebAssembly вы не можете делать переключение стека внутри WebAssembly. Поэтому горутины на самом деле не работают, когда вы компилируете их в браузере. Они работают, потому что у них есть часть, написанная на JavaScript. Когда вы вызываете что-то, что должно ожидать, они как бы перепланируют это, а затем перескакивают между JavaScript обратно в модуль WebAssembly, и создается впечатление, что goroutines выполняются параллельно, но это явно не работает в Lunatic, потому что у нас нет среды выполнения JavaScript, и у нас нет этой хост-функции, которая бы позаботилась об этом.

Поэтому вы можете просто использовать нашу модель параллелизма в Go — у вас не будет goroutines, но вы можете использовать процессы и почтовые ящики. И это действительно зависит от языка, я думаю, от системы типов и возможностей, но, возможно, это может быть приятно в Go — у вас будет что-то вроде того, как если бы вы писали на Go, как на Erlang. И я думаю, что простые примеры, такие как чтение из файлов, работают из коробки прямо сейчас, их можно просто скомпилировать, они выполняются без проблем. Но эта часть все еще нуждается в том, чтобы кто-то внес свой вклад, чтобы вы позаботились об открытии сетевых соединений, например. Это еще не является частью WASI, поэтому над этим нужно будет поработать.

Jonn: Итак, для меня, если я хочу, скажем, взаимодействовать с программами на Go, мне нужно будет написать обертку на Rust, а затем проксировать сообщения в stdin.

Бернард: Функции Lunatic для отправки сообщений довольно просты, просто как запись в этот буфер. этот фрагмент памяти, а затем просто отправить его этому идентификатору процесса. Если вы создадите несколько таких оберток, вы уже сможете использовать возможности отправки сообщений, так что вам не нужно проксировать стандартный ввод. Я думаю, что это не будет слишком большой работой, даже если вы просто вручную закодируете те небольшие части, которые вам нужно сделать.

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

С Lunatic вы также получаете возможность писать на разных языках. Теперь вы не на Erlang, но у вас есть модуль WebAssembly, написанный на Rust, и другой на Go, и они тоже общаются. Хотя микросервисы обычно общаются по HTTP, они используют «родную» передачу сообщений. Она более производительна, потому что проскакивает через меньшее количество стеков. Но на данный момент, как я уже говорил, мы в основном сфокусированы на Rust, поэтому мы не изучили весь этот мир взаимодействия разных языков и то, как это должно работать.

Я знаю, что с C и Rust все просто, потому что вы всегда можете связать код C внутри Rust, так что вы можете позаботиться о некоторых библиотеках, если вам нужна библиотека C, в некоторых случаях это работает. Но также важно упомянуть, что иногда вы ограничены в том, что вы можете скомпилировать в WebAssembly, потому что некоторые C-коды просто содержат встроенные ассемблеры. Вы не можете взять любую сборку x86 и скомпилировать ее в WebAssembly, это не имеет смысла. Поэтому некоторые приложения даже не компилируются, компилятор не будет работать, так что всегда есть ограничения, но я думаю, что это может создать интересную архитектуру.

Вы бы порекомендовали Lunatic тем, кто только начинает изучать WASM?

Jonn: Порекомендуете ли вы Lunatic как способ познакомиться с миром WASM новичкам вроде меня, которые, возможно, знают Rust или Erlang, но ничего не знают о WASM, и если да, то не могли бы вы выделить несколько репозиториев, из которых мы, новички в WASM, могли бы черпать вдохновение?

Бернард: Есть, я думаю, две дороги, по которым можно пойти с WebAssembly. Первая и самая, я бы сказал, распространенная, которую делают люди: «Я хочу запустить в браузере код на Rust, код на C». Lunatic не сможет вам сильно помочь в этой части, потому что мы сейчас в основном сосредоточены на бэкэнде. Есть много замечательных библиотек, которые вы можете использовать. Например, для Rust, bindgen, она поможет вам в основном сопоставить типы Rust с типами JavaScript и сделать взаимодействие между ними беспрепятственным. Так что если вы ориентируетесь на браузер и хотите запускать Rust в браузере, это отличное решение.

Но если вы хотите использовать WebAssembly на задней стороне, я думаю, что Lunatic — отличный выбор. Мы достаточно стабильны. Мы уже два года в разработке. Мы прошли через несколько переписываний и итераций, но на данный момент он работает хорошо.

Я написал приложение для Telnet-чата, я думаю, что это хорошая отправная точка, потому что оно не такое уж тривиальное. Во-первых, оно также использует некоторые зависимости Rust, которые компилируются в WebAssembly, и все работает. Итак, в основном, как это работает, это Telnet, это действительно простой протокол, и вы подключаетесь к этому серверу, и каждое нажатие клавиши, которое вы набираете, отправляется на сервер. Пользовательский интерфейс отображается на сервере, а затем просто диффузия управляющих последовательностей отправляется обратно на ваш терминал, и он изменяется. Это немного похоже на Phoenix LiveView, но для терминала, если вы знакомы с этим.

[…]

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


Мы хотели бы поблагодарить Бернарда за то, что он стал гостем этого эпизода!

Если вы хотите узнать о нем больше, вы можете следить за ним или за Lunatic runtime в Twitter. Кроме того, вы можете проверить Lunatic на GitHub или присоединиться к их discord.

Если вы хотите услышать больше из нашего подкаста, не забудьте подписаться на нас на вашей любимой подкаст-платформе!

Оцените статью
Procodings.ru
Добавить комментарий