В 2018 году мой тогдашний работодатель (car2go) дал мне возможность поиграть с довольно молодым языком программирования Crystal. Чтобы получить полное представление о языке и его доступных фреймворках, я перенес микросервис с Rails на Amber (https://github.com/amberframework/amber ).

Что делает Crystal особенным?

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

Синтаксис Ruby и стандартная библиотека

Если вам нравится синтаксис Ruby, вам также понравится синтаксис Crystal, поскольку оба в основном идентичны. Есть несколько незначительных отличий, но они быстро адаптируются - на самом деле, на https://github.com/crystal-lang/crystal/wiki/Crystal-for-Rubyists вы можете найти многие из них.

Вдобавок Crystal черпал вдохновение из стандартной библиотеки Ruby. Например, обработка массивов и хэшей очень удобна с помощью бесчисленных методов Enumerable Interface (https://crystal-lang.org/api/0.29.0/Enumerable.html).

Типы безопасности

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

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

Nil / Null - независимый тип

Эта функция может сначала показаться очень банальной, но это мощный механизм предотвращения NullPointerExceptions. Большинство языков рассматривают значения nil / null как нечто особенное, присваиваемое параметрам / переменным всех типов.

В Crystal, с другой стороны, есть специальный тип Nil, который имеет собственный ограниченный набор методов. Если вы попытаетесь выполнить для него неопределенный метод, во время компиляции возникнет ошибка.

Скорость исполнения

Crystal может похвастаться тем, что он «быстр как C», что в основном основано на использовании LLVM для компиляции. Как мы увидим позже, это действительно быстро, особенно если исходить из интерпретируемого языка, такого как Ruby (да!).

Документация

Документация Crystal действительно великолепна для его юного возраста. Сопровождающие язык предоставляют очень обширное руководство (https://crystal-lang.org/reference/), которое охватывает все языковые функции, а также прекрасную документацию по API (https://crystal-lang.org/ api ).

… И многое другое

Типы объединения, перегрузка методов, абстрактные классы, перечисления, обобщения, волокна, макросы и все, что я забыл.

Первый тест

Прежде чем я приступил к переносу всего сервиса, я начал экспериментировать с простым примером веб-сервера. Я протестировал Sinatra для Ruby и его клона Crystal Kemal, написав почти идентичный код для обоих фреймворков. Для нагрузочного тестирования я использовал wrk2 с 10 параллельными подключениями.

Результаты были очень показательными. Кемаль достиг примерно 20x запросов в секунду. Я также измерил 99-й процентиль для времени прохождения туда и обратно, и они показали тактовую частоту 10,7 мс против 0,4 мс, так что также впечатляющее улучшение .

Перенос микросервиса реального мира

Искусственные тесты всегда немного неприятны, поэтому перенос реального приложения Rails в Crystal был для меня более захватывающим.

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

Проблемы развития

Несмотря на то, что Ruby и Crystal очень похожи, а Amber пытается быть клоном Rails, перенос приложения не сводился к копированию и вставке кода. Вместо этого мне пришлось почти создать приложение Crystal с нуля в стиле тестирования.

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

Во второй статье я обязательно остановлюсь на процессе разработки. Это достаточно глубокая для себя тема. Для этого давайте просто сосредоточимся на результатах тестов.

Контрольные точки

Как и в случае сравнения Sinatra и Kemal, показанного выше, я измерил количество запросов в секунду для обоих приложений. Amber превзошла приложение Rails в 20 раз (6000 против 300). 99-й процентиль времени двустороннего обращения составил 2,65 мс против 31,7 мс.

Еще я замерил потребление памяти во время выполнения. И здесь Amber выиграла с 6,5 МБ против 50 МБ.

Когда я создал оба приложения на основе Docker, я также смог сравнить размер обоих изображений. Следует отметить, что оба изображения были основаны на Альпах. Тем не менее размер образа Amber составлял 13 МБ, в то время как приложению Rails требовалось 380 МБ для своего образа.

Огромную разницу можно объяснить тем, что образ Amber содержал только двоичный исполняемый файл и несколько собственных библиотек. С другой стороны, образ Rails требует наличия интерпретатора Ruby и установки всех Gems для его выполнения. Я уверен, что вы определенно можете уменьшить размер этого изображения на хороший кусок, но я сомневаюсь, что вы даже приблизитесь к размеру изображения Amber.

Крошечный забавный тест, который я предпринял, заключался в измерении строк кода между обоими приложениями. Здесь оба были фактически равны: 579 против 574.

Как показано, перенос приложения Ruby / Rails на Crystal / Amber может привести к значительному повышению производительности с точки зрения скорости выполнения и использования памяти. Мне лично очень понравилось работать с Crystal, и я могу только предложить каждому разработчику попробовать этот молодой язык.

Если у вас возникнут вопросы или пожелания, оставьте, пожалуйста, комментарий!