Сравнение golang и rust

Обновлено: 06.07.2024

Как написал автор статьи gobwas (здесь и далее орфография сохранена):

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

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

В статье будет много копипасты из исходной статьи, но я надеюсь, что мне это простят.

Суть тестов

При тестировании выяснилось, что все претенденты работают примерно с одинаковой производительностью в такой постановке — все упиралось в производительность V8. Однако реализация задания не была лишней — разработка на каждом из языков позволила составить значительную часть субъективных оценок, которые так или иначе могли бы повлиять на окончательный выбор.

Итак, мы имеем два сценария. Первый — это просто приветствие по корневому URL:


Второй — приветствие клиента по его имени, переданному в пути URL:

Первоначальный исходный код тестов

Подлый удар в спину

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

Дело в том, что в примере Node.js и Go компиляция регулярного выражения происходит единожды, тогда как в Rust компиляция выполняется на каждый запрос. Про Scala ничего сказать не могу.

Выдержка из документации к regex для Rust:

Example: Avoid compiling the same regex in a loop

It is an anti-pattern to compile the same regular expression in a loop since compilation is typically expensive. (It takes anywhere from a few microseconds to a few milliseconds depending on the size of the regex.) Not only is compilation itself expensive, but this also prevents optimizations that reuse allocations internally to the matching engines.

In Rust, it can sometimes be a pain to pass regular expressions around if they're used from inside a helper function. Instead, we recommend using the lazy_static crate to ensure that regular expressions are compiled exactly once.


Specifically, in this example, the regex will be compiled when it is used for the first time. On subsequent uses, it will reuse the previous compilation.

Выдержка из документации к regex для Go:

But you should avoid the repeated compilation of a regular expression in a loop for performance reasons.

Как допустили такую ошибку? Я не знаю… Для такого прямолинейного теста это является существенной просадкой в производительности, ведь даже в комментариях автор указал на тормознутость регулярок:
Спасибо! Я тоже думал было переписать на split во всех примерах, но потом показалось, что с regexp будет более жизненно. При оказии попробую прогнать wrk со split.

Восстанавливаем справедливость

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

Окружение

Все тесты запускались на локалхосте, без всяких виртуалок, ибо лень. Будет замечательно, если автор предоставит бенчмарки со своего железа, я вставлю апдейтом, вот специально для него репозиторий с тестами, где, кстати, зафиксированы растовые библиотеки на момент написания оригинальной статьи 2015.12.17 (я надеюсь, что все).

  1. Ноут
    • Intel® Core(TM) i7-6820HQ CPU @ 2.70GHz, 4+4
    • CPU Cache L1: 128 KB, L2: 1 MB, L3: 8 MB
    • 8+8 GB 2133MHz DDR3

  2. Десктоп
    • Intel® Core(TM) i3 CPU 560 @ 3.33GHz, 2+2
    • CPU Cache L1: 64 KB, L2: 4 MB
    • 4+4 GB 1333MHz DDR3

  3. go 1.6.2, released 2016/04/20
  4. rust 1.5.0, released 2015/12/10. Да, я специально взял старую версию Rust.
  5. Простите, любители Scala и Node.js, этот холивар не про вас.

Интрига

Попробуем выполнить 50 000 запросов за 10 секунд, с 256 возможными параллельными запросами.

Десктоп

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

Ноут на подзарядке


Выводы

И всё это время Rust совершенствовался, посмотрите на «The Computer Language Benchmarks Game» Rust vs Go за 2015 и 2017 года. Отрыв в производительности только растет.

Если тебе, дорогой читатель, по нраву Go, пиши на нём. Но не стоит сравнивать его производительность с Rust, ибо она будет не в пользу твоего любимого языка, уж точно не на таких синтетических тестах.

Я надеюсь, что я был объективен и непредвзят, справедливость восторжествовала, а моя статья не содержит ошибок.




На протяжении последних нескольких недель мне довелось использовать Go в работе. Я впервые использовал Go на более-менее крупном и серьезном проекте. До этого я достаточно много читал про Go и практиковался на примерах и небольших программах при изучении возможностей Rust, но реальное программирование — это совсем другое дело.

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

Общие впечатления

Программировать на Go приятно. В библиотеках было все, что мне было нужно, без слишком большого количества недоработок. Изучение тоже было приятным опытом — это хорошо продуманный и практичный язык. Как пример, узнав синтаксис, вы увидите, что многие идиомы из других языков переносятся на Go. Как только вы освоите часть Go, можно легко предсказать его возможности. Имея некоторый опыт других языков, я мог без проблем читать и понимать код Go без особой помощи гугла.

Я отметил намного меньше фрустрации и намного больше продуктивности, чем при использовании C/C++, Java, Python и т. д. Однако, Go все еще ощущается частью этого поколения языков. Он извлек из них уроки, и я думаю, что это, вероятно, лучший язык этого поколения; но он определенно часть этого поколения. Он скорее представляет инкрементное улучшение, нежели нечто принципиально новое (следует отметить, что это не оценочное суждение — инкрементность зачастую на пользу в мире разработки программного обеспечения). Хорошим примером этого является nil: такие языки как Rust и Swift избавились от null парадигмы, тем самым устранив целый класс ошибок. Go делает ее менее опасной: нет нулевых значений; разграничение nil и 0. Но основная идея все еще присутствует, как и распространенная рантайм ошибка разыменования нулевого указателя.

Легкость освоения

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

Несколько факторов, в пользу обучаемости:

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

Рутинность кода

Код Go очень быстро становится повторяющимся. В нем отсутствует какой-либо механизм типа макросов или дженериков для сокращения повторений, (интерфейсы хороши для абстракций, но не так хорошо работают для уменьшения дублирования кода). У меня часто накапливается большое количество функций, идентичных за исключением типов.

Обработка ошибок также способствует повторению. Многие функции имеют больше шаблонов if err != nil < return err >, чем оригинального кода.

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

То, что мне понравилось

  • Время компиляции. Определенно быстро; определенно намного быстрее чем Rust. Но на самом деле не так быстро, как я ожидал (мне кажется, что оно на уровне C/C ++ или даже немного быстрее для средних и крупных проектов, а я ожидал чего-то практически мгновенного).
  • Горутины и каналы. Облегченный синтаксис для запуска подпрограмм Go и использования каналов — это действительно хорошо. Такая небольшая деталь, делающая параллельное программирование намного приятнее, чем в других языках, действительно показывает силу синтаксиса.
  • Интерфейсы. Они лишены изыска, но их легко понять и использовать, и они полезны во многих местах.
  • if . ; . < >синтаксис. Возможность ограничивать область видимости переменных телом if операторов — это хорошо. Это нечто сродни if let в Swift и Rust, но более общего назначения (Go не имеет паттерн матчинга как Swift и Rust, поэтому он не может использовать if let).
  • Тест и док комментарии просты в использовании.
  • Инструмент Go приятен — все сразу в одном месте без необходимости подключения множества инструментов через командную строку
  • В наличии сборщик мусора (GC)! Отсутствие необходимости заботиться об управлении памятью действительно делает программирование проще
  • Varargs.

То, что мне не понравилось

Порядок значения не имеет.

  • nil слайсы — nil, nil слайс и пустой слайс — все это разные вещи. Я более чем уверен, что вам нужны только двое из них, а не все три.
  • Никаких первичных классов. Использование констант ощущается непривычно.
  • Запрет на циклы импорта. Это действительно ограничивает полезность пакетов предназначенных для модуляризации проекта, поскольку это поощряет набивать большое количество файлов в один пакет (или множество небольших пакетов, что может быть так же плохо, если файлы, которые должны быть вместе, распределены в разные пакеты).
  • switch может быть не исчерпывающим
  • for . range возвращает пару индекс/значение. Получить только индекс легко (просто проигнорируйте значение), но получение только значений требует явного указания. Для меня это шиворот-навыворот, так как в большинстве случаев мне нужно значение, а не индекс.
  • Синтаксис:
    • Несоответствие между определениями и использованиями.
    • Избирательность компилятора (требующего или запрещающего, например, висящие запятые); в основном это облегчается хорошим набором инструментов, но существует несколько случаев, когда это создает раздражающий дополнительный шаг.
    • При использовании возвращаемых типов с несколькими значениями скобки требуются для типа, но не для return.
    • Для объявления структуры требуется два ключевых слова (type и struct).
    • Использование заглавных букв для обозначения переменных public или private. Это как венгерская нотация, только хуже.

    Согласованность

    Как разработчик языка и программист, меня, вероятно, больше всего удивило то, что в Go часто встречается несоответствие между тем, что встроено, и тем, что доступно пользователям. Задача многих языков — развеять как можно больше магии и сделать доступными встроенные функции для пользователей. Перегрузка операторов — простой, но противоречивый пример. В Go таится очень много магии! И вы очень легко врезаетесь в стену неспособности делать то, что могут встроенные вещи.

    Некоторые моменты, которые особенно выделяются:

    • Есть хороший синтаксис для возврата нескольких значений и для каналов, но их нельзя использовать вместе, потому что нет типов кортежей.
    • Существует оператор for . range для итерации по массивам и слайсам, но вы не можете перебирать другие коллекции, потому что концепция итераторов отсутствует.
    • Такие функции, как len и append , являются глобальными, но невозможно сделать ваши собственные функции глобальными. Эти глобальные функции работают только со встроенными типами. Они также могут быть универсальными, даже если в Go нет дженериков!
    • Нет перегрузки операторов. Это особенно раздражает при использовании == , потому что это означает, что вы не можете использовать пользовательские типы в качестве ключей для map, если они не сопоставимы. Это свойство является производным от структуры типа и не может быть переопределено программистом.

    Заключение

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

    По сравнению с Rust, Go — это совсем другой язык. Хотя оба они могут быть грубо описаны как системные языки или «замена» для C, они имеют разные цели и приложения, стили языкового дизайна и приоритеты. Сборка мусора — это действительно большая разница. Наличие GC в Go делает язык намного проще и меньше, и его легче понимать. Отсутствие GC в Rust делает его очень быстрым (особенно если вам нужна четкая задержка, а не просто высокая пропускная способность) и обеспечивает возможности и шаблоны программирования, которые невозможны в Go (по крайней мере, без ущерба для производительности).

    Go — это компилируемый язык с хорошо реализованной средой выполнения. Он быстр. Rust также компилируемый, но имеет намного меньшую среду выполнения. Он очень быстр. Предполагая, что никаких других ограничений нет, я думаю, что выбор между использованием Go и Rust — это компромисс между гораздо более короткой кривой обучения и более простыми программами (что означает более быструю разработку) и, со стороны Rust, большой скоростью и более выразительной системой типов (что делает ваши программы более безопасными и ускоряет отладку и поиск ошибок).

    Эта статья посвящена разбору моего эксперимента по написанию небольшого инструмента командной строки с использованием двух языков, в программировании на которых у меня не особенно много опыта. Речь идёт о Go и Rust.


    Если вам не терпится увидеть код и самостоятельно сравнить один вариант моей программы с другим — то вот репозиторий Go-варианта проекта, а вот — репозиторий его варианта, написанного на Rust.

    Обзор проекта

    У меня есть домашний проект, который я назвал Hashtrack. Это — небольшой сайт, фуллстек-приложение, которое я написал для технического собеседования. Работать с ним очень просто:

    1. Пользователь аутентифицируется (учитывая то, что он уже создал себе учётную запись).
    2. Он вводит хештеги, за появлением которых в Твиттере он хочет наблюдать.
    3. Он ждёт появления на экране найденных твитов с заданным хештегом.

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

    Возможности инструмента командной строки

    Вот описание основных возможностей, в частности — команд, которые мне хотелось реализовать в моём инструменте командной строки.

    • hashtrack login — вход в систему, то есть — создание сессионного токена и его сохранение в локальной файловой системе, в конфигурационном файле.
    • hashtrack logout — выход из системы, то есть — удаление сессионного токена, сохранённого локально.
    • hashtrack track <hashtag> [. ] — начало наблюдения за хештегом или за несколькими хештегами.
    • hashtrack untrack <hashtag> [. ] — окончание наблюдения за хештегом или за несколькими хештегами.
    • hashtrack tracks — вывод списка хештегов, за которыми ведётся наблюдение.
    • hashtrack list — вывод 50 последних найденных твитов.
    • hashtrack watch — вывод найденных твитов в реальном времени.
    • hashtrack status — вывод сведений о пользователе в том случае, если был осуществлён вход в систему.
    • Инструмент должен поддерживать опцию командной строки --endpoint , которая позволяет настраивать его на работу с различными серверами.
    • Должна поддерживаться опция командной строки --config , позволяющая загружать конфигурационные файлы.
    • В конфигурационных файлах должно присутствовать свойство endpoint .

    Почему я решил использовать именно Go и Rust?

    Есть много языков, на которых можно писать инструменты командной строки.

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

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

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

    Тут мне бы хотелось упомянуть языки Crystal и Nim. Они выглядят многообещающе. Я с нетерпением жду возможности испытать их в очередном своём проекте.

    Локальное окружение

    Перед использованием нового набора инструментов я всегда интересуюсь удобством работы с ним. А именно, тем, придётся ли мне использовать некий менеджер пакетов для глобальной установки программ в системе. Или, что кажется мне гораздо более удобным решением, можно ли будет устанавливать всё, ориентируясь на учётную запись пользователя. Мы говорим о менеджерах версий, они упрощают нам жизнь, ориентируясь при установке программ на пользователей, а не на систему в целом. В среде Node.js с этой задачей отлично справляется NVM.

    При работе с Go для тех же целей можно пользоваться GVM. Этот проект отвечает за локальную установку программ и за управление версиями. Установить его очень просто:


    Готовя среду разработки на Go, нужно знать о существовании двух переменных окружения — GOROOT и GOPATH . Подробности о них можно почитать здесь.

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

    В итоге я просто использовал в директории проекта GOPATH=$(pwd) . Главный плюс этого заключался в том, что в моём распоряжении оказалась система работы с зависимостями, ограниченная рамками отдельного проекта, нечто вроде node_modules . Эта система показала себя хорошо.

    После того, как я окончил работу над моим инструментом, я обнаружил, что существует проект virtualgo, который помог бы мне решить проблемы с GOPATH .

    У Rust есть официальный установщик rustup, который выполняет установку набора инструментальных средств, необходимого для использования Rust. Rust можно установить буквально одной командой. Кроме того, при использовании rustup у нас есть доступ к дополнительным компонентам, к таким, как сервер rls и система форматирования кода rustfmt. Многие проекты требуют ночных сборок набора инструментов Rust. Благодаря применению rustup у меня не возникло проблем с переключением между версиями.

    Поддержка редактора

    Я пользуюсь VS Code и смог найти расширения, предназначенные для Go и для Rust. Оба языка отлично поддерживаются в редакторе.

    Для отладки Rust-кода мне, следуя этому руководству, понадобилось установить расширение CodeLLDB.

    Управление пакетами

    В экосистеме Go нет менеджера пакетов или даже официального реестра. Здесь система разрешения модулей основана на импорте модулей с внешних URL.

    Rust использует для управления зависимостями менеджер пакетов Cargo, который загружает пакеты с crates.io, из официального реестра для Rust-пакетов. У пакетов из экосистемы Crates может быть документация, размещённая на docs.rs.

    Библиотеки

    Если говорить о Go, то мне удалось найти несколько библиотек, вроде machinebox/graphql и shurcooL/graphql. Вторая из них использует структуры для маршалинга и анмаршалинга данных. Поэтому я выбрал именно её.

    Я использовал форк shurcooL/graphql, так как мне нужно было настраивать на клиенте заголовок Authorization . Изменения представлены этим PR.

    Вот пример вызова мутации GraphQL, написанный на Go:


    Ни одна из библиотек для Go и для Rust не поддерживала работу с GraphQL по протоколу WebSocket.

    На самом деле, библиотека graphql_client поддерживает подписки, но, так как она независима от протоколов, мне пришлось самостоятельно реализовать механизмы WebSocket-взаимодействия с GraphQL.

    Для использования WebSocket в Go-версии приложения библиотеку нужно было модифицировать. Так как я уже использовал форк библиотеки, мне этого делать не захотелось. Вместо этого я использовал упрощённый способ «наблюдения» за новыми твитами. А именно — я, для получения твитов, каждые 5 секунд отправлял запросы к API. Я не горжусь тем, что поступил именно так.

    При написании программ на Go можно пользоваться ключевым словом go для запуска легковесных потоков, так называемых горутин. В Rust же используются потоки операционной системы, делается это посредством вызова Thread::spawn . Для передачи данных между потоками и там и там используются каналы.

    Обработка ошибок

    В Go ошибки рассматриваются так же, как любые другие значения. Обычный способ обработки ошибок в Go заключается в проверке их наличия:


    В Rust есть перечисление Result<T, E> , которое включает в себя значения, выражающие успешное завершение операции и завершение операции с ошибкой. Это, соответственно, Ok(T) и Err(E) . Здесь есть ещё одно перечисление, Option<T> , включающее в себя значения Some(T) и None . Если вы знакомы с Haskell, то вы можете узнать в этих значениях монады Either и Maybe .

    Тут, кроме того, есть «синтаксический сахар», имеющий отношение к распространению ошибки (оператор ? ), который разрешает значение структуры Result или Option и автоматически возвращает Err(. ) или None в том случае, если что-то идёт не так.


    Этот код является эквивалентом следующего кода:


    Итак, в Rust имеется следующее:

    • Монадические структуры ( Option и Result ).
    • Поддержка оператора ? .
    • Типаж From , используемый для автоматического преобразования ошибок при их распространении.

    Время компиляции

    Go — это язык, который был создан с учётом того, чтобы код, написанный на нём, компилировался бы как можно быстрее. Изучим этот вопрос:


    Впечатляет. Посмотрим теперь на то, что нам покажет Rust:


    Здесь выполняется компиляция всех зависимостей, а это 214 модулей. При повторном запуске компиляции всё уже подготовлено, поэтому данная задача выполняется практически мгновенно:


    Как видите, Rust использует инкрементную модель компиляции. Выполняется частичная повторная компиляция дерева зависимостей, начиная с изменённого модуля и заканчивая модулями, которые от него зависят.

    На выполнение release-сборки проекта уходит больше времени, что вполне ожидаемо, так как компилятор при этом выполняет оптимизацию кода:

    Непрерывная интеграция

    Те особенности компиляции проектов, написанных на Go и на Rust, которые мы выявили выше, проявляются, что вполне ожидаемо, в системе непрерывной интеграции.


    Обработка Go-проекта


    Обработка Rust-проекта

    Потребление памяти

    Для анализа потребления памяти разными версиями моего инструмента командной строки я воспользовался следующей командой:


    Команда time -v выводит много интересных сведений, но меня интересовал показатель процесса Maximum resident set size , который представляет собой пиковый объём физической памяти, выделенной программе в процессе её выполнения.

    Вот код, который я применил для сбора данных о потреблении памяти разными версиями программы:


    Вот результаты для Go-версии:


    Вот — сведения о потреблении памяти Rust-версией программы:


    Эта память выделяется в ходе решения следующих задач:

    В Go есть сборщик мусора, который используется для обнаружения неиспользуемой памяти и её освобождения. Программист, в итоге, на эти задачи не отвлекается. Так как в основе сборщика мусора лежат эвристические алгоритмы, его использование всегда означает необходимость идти на компромиссы. Обычно — между производительностью и объёмом памяти, используемой приложением.

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

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

    Причины, по которым я выбрал бы Go

    Я выбрал бы для некоего проекта Go по следующим причинам:

    • Если бы мне нужен был язык, который легко будет изучить членам моей команды.
    • Если бы мне хотелось писать простой код за счёт меньшей гибкости языка.
    • Если бы я разрабатывал программы только для Linux, или если бы Linux была бы операционной системой, представляющей для меня наибольший интерес.
    • Если бы важным было время компиляции проектов.
    • Если бы мне нужны были зрелые механизмы асинхронного выполнения кода.

    Причины, по которым я выбрал бы Rust

    Вот причины, которые могут привести к тому, что я выберу для некоего проекта Rust:

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

    Общие замечания

    У Go и Rust есть некоторые особенности, которые до сих пор не дают мне покоя. Речь идёт о следующем:

    • Go так сильно нацелен на простоту, что иногда это стремление даёт противоположный эффект (например, как в случаях с GOROOT и GOPATH ).
    • Я всё ещё толком не пойму концепцию «времени жизни» в Rust. Меня выводят из равновесия даже попытки поработать с соответствующими механизмами языка.

    Могу сказать, что и Go и Rust — это языки, которые было очень интересно изучать. Я считаю их отличными дополнениями к возможностям мира C/C++-программирования. Они позволяют создавать приложения самой разной направленности. Например — веб-сервисы и даже, благодаря WebAssembly, клиентские веб-приложения.

    Итоги

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

    Если вы хотите почитать ещё что-нибудь, посвящённое сравнению Go и Rust, взгляните на эту статью. В ней, кроме прочего, поднят вопрос, касающийся серьёзных проблем с многоплатформенной совместимостью программ.

    Какой язык вы использовали бы для разработки инструмента командной строки?


    В этой статье мы обсудим очертания Руста против Голанга. Rust - это язык со статическим управлением памятью, но Golang - это язык с эффективным сборщиком мусора. Если я начну сравнивать эти два языка, я смогу написать страницы об обоих. Синтаксис Rust похож на C ++ и был разработан для правильного параллелизма. Грейдон Хоар разработал Rust в июле 2010 года. Rust быстрее, и в Rust также гарантируется более высокая производительность. Голанг был разработан в Google Гриземером, Робом Пайком и Кеном Томпсоном. Синтаксис Golang похож на C, и улучшение было сделано в структурной типизации и сборке мусора. Это язык программирования с открытым исходным кодом.

    Язык программирования Rust

    Rust был разработан на C ++ с более безопасными сценариями в 2010 году. Язык с открытым исходным кодом. Rust имеет высокопроизводительный граф по сравнению с языками C ++ или C. У ржавчины много фигурных скобок, и отступы вообще не нужны. Управление памятью осуществляется через соглашение RAII в Rust. Компилятор Rust может определить тип переменной, аргумента, функции из контекста или синтаксиса, который он набрал. Теперь TypeState удаляется из Rust, что достигается с помощью шаблона брендинга.

    В Rust есть шаблон Builder, который позволяет описать текущее состояние объекта в тип этого объекта. Rust не имеет определенных классов, но работает со структурами типов и реализациями. Было много изменений, пока версия была обновлена ​​в Rust. Эта причина сделала Rust менее популярным среди разработчиков. Наследование и полиморфизм поддерживаются в Rust. В Русте нет автоматической сборки мусора. Безопасный Rust и небезопасный Rust заставляет пользователей выбирать программирование на языке Rust для более безопасной разработки. Rust действует как язык сценариев низкого уровня.

    Синтаксис:

    fn main () (
    println! ("Hello World!");
    )
    println! is the macro in this program.

    Голанг (язык программирования)

    Разработанный в 2010 году, язык стал проще и более параллельным. Синтаксис очень похож на C, и компилятор написан на C ++. Хотя Golang не является динамическим языком, он поддерживает шаблоны, адаптированные к среде. У Golang есть пакеты, которые позволяют создавать зависимости. IDE для Golang включают игровую площадку Go, автоответчик и многое другое. Golang - это язык программирования с открытым исходным кодом. Golang изначально предназначался для облачного программного обеспечения. Это также помогает в создании сложного программного обеспечения. Голанг хорош для системного программирования.

    Природа Golang похожа на динамические языки, учитывая синтаксис и рабочую среду. У Голанга есть онлайн-пакетная документация. Golang имеет много встроенных типов как C, а также доступен с указателями. Выражения классифицируются как печатные или нетипизированные. Концепция класса недоступна в Голанге. Возможности для замены концепции класса - встраивание и интерфейсы. Встраивание обеспечивает композицию и интерфейсы для обеспечения полиморфизма во время выполнения. Голанг структурно типизирован. У каждой упаковки Голанга есть свой путь. Параллелизм - главная особенность Голанга, так как это легкий процесс, доступность различных библиотек, наличие каналов и так далее. Инструмент Gofmt стандартизирует отступы, интервалы и другие детали. Голанг все еще стандартизируется с каждым выпуском версии.

    Синтаксис:

    package main
    import “ fmt”
    func main () (
    fmt.Println (“Hello World”)
    This program prints Hello World in Golang.

    Сравнение лицом к лицу между Рустом и Голангом (Инфографика)

    Ниже приведены Топ-21 сравнений между Rust и Golang :


    Ключевые различия между Рустом и Голангом

    Давайте обсудим некоторые из основных ключевых различий между Rust и Golang :

    • В то время как Rust называется безопасным и параллельным языком программирования, Golang называется статически типизированным, скомпилированным языком программирования.
    • Голанг легче по сравнению с Рустом. Из-за безопасной и защищенной кодовой среды, Rust немного сложно для разработчиков.
    • У Rust есть надлежащий параллелизм, в то время как у Голанга хорошая поддержка параллелизма.
    • Ржавчина быстрее по сравнению с Голангом.
    • Rust имеет статическое управление памятью, в то время как Golang имеет эффективный сборщик мусора.
    • Мы не сможем писать код Rust на нескольких платформах, пока мы можем назвать Golang кроссплатформенным.
    • Многопоточность выполняется эффективно на обоих языках, но Голанг делает это легко и с помощью простого кода.
    • Проверка ошибок выполняется в Rust с помощью компилятора, а в Golang опция проверки ошибок предоставляется разработчику.
    • У нас есть много библиотек на Python, в то время как в Rust у нас их немного.
    • Безопасность памяти так хороша в Rust, но в Python это не так.

    Сравнительная таблица Руста против Голанга

    В таблице ниже приведены сравнения между Рустом и Голангом :

    Ржавчина Golang
    Ржавчина быстрее по сравнению с Голангом.Голанг не быстрее.
    Ржавчина не имеет сборщика мусора.Голанг имеет сборщик мусора.
    Руст использует абстракции вместо классов.Голанг использует интерфейсы вместо классов.
    Rust не так хорош в разработке программного обеспечения по сравнению с Golang.Golang хорош для разработки корпоративного программного обеспечения.
    Rust заставляет разработчиков кодировать в безопасной среде.Голанг не заставляет разработчиков писать безопасный код.
    Обработка ошибок является сложной из-за компилятора.Обработка ошибок на риск разработчика.
    Производительность Rust лучше по сравнению с Golang.Производительность не так уж велика на Голанге.
    Читаемость не очень хорошая.Читаемость лучше по сравнению с Rust.
    Использование ржавчины ограничено несколькими ресурсами.Использование Голанга шире, так как используются многие приложения.
    Rust использует компилятор для запуска программы.Сборник Голанга не очень хорош.
    Ржавчина не хороша в сетевом общении.Голанг помогает неэффективной связи сетей.
    Руст имеет общий код.Голанг не имеет дженериков.
    Ржавчина не имеет синхронизирующего механизма.Голанг имеет синхронизирующий механизм.
    Ржавчина - высший язык.Голанг не превосходит других языков.
    Программа Rust имеет расширение .rs.Программа Golang имеет расширение .go.
    Ржавчина создается в Mozilla.Голанг создан в Google.
    Rust совместим со многими другими языками.Голанг совместим только с несколькими языками.
    На данный момент Rust не используется в машинном обучении и науке о данных.Golang может использоваться в проектах машинного обучения и Tensor flow благодаря своим обширным библиотекам.
    Rust использует операторы соответствия.Голанг использует операторы switch.
    Rust использует диапазон для итерации чисел.Голанг использует цикл для итерации чисел
    Ржавчина более функциональна.Голанг не настолько функционален.

    Вывод

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

    Рекомендуемые статьи

    Это руководство по Руст против Голанга. Здесь мы также обсудим ключевые различия между Rust и Golang с помощью инфографики и сравнительной таблицы. Вы также можете взглянуть на следующие статьи, чтобы узнать больше -

    Java

    Мне показалось интересным провести сравнение между Java, Go и Rust. Речь идет не о бенчмарке, а о сравнении таких характеристик, как размер выходного исполняемого файла, использование памяти и CPU, требования к среде выполнения и, конечно, небольшой тест для того, чтобы получить показатели по количеству запросов в секунду и попытаться разобраться в цифрах.

    В попытке сравнить яблоки с яблоками (наверно, можно так сказать) я написал веб-сервис на каждом из языков, подлежащих сравнению. Он довольно простой и обслуживает три конечные точки REST.


    Конечные точки, обслуживаемые веб-сервисом, в Java, Go и Rust.

    Репозиторий для трех веб-сервисов располагается на github.

    Размер артефакта

    Начнем с информации о том, как создавались двоичные файлы. В случае с Java я создал всё в большом толстом JAR-файле при помощи maven-shade-plugin и выполнил mvn package для сохранения проекта в целевую папку. Для сборки проекта в Go был использован go build , а в Rust — cargo build --release .


    Размер каждой скомпилированной программы в мегабайтах

    Размер скомпилированного артефакта также зависит от выбранных библиотек/зависимостей, поэтому если они раздуты, то в конечном итоге ваша скомпилированная программа будет такой же. В диаграмме выше указан размер скомпилированных программ с учетом выбранных библиотек в нашем конкретном случае.

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

    Использование памяти

    Состояние простоя


    Использование памяти каждым приложением в состоянии простоя

    Минуточку! А где же столбцы для версий Go и Rust, показывающие объем требуемой памяти во время простоя? Они там тоже есть, но только Java потребляет более 160 МБ, когда JVM запускает программу, и далее сидит без дела, ничего не выполняя. В случае с Go программа использует 0,86 МБ, с Rust — 0,36 МБ. Видите разницу?! В этом примере Java использует гораздо больше памяти, чем Go и Rust, просто ничего не делая. А это огромные затраты ресурсов.

    Обслуживание REST-запросов

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

    Приведенная выше команда wrk сообщает следующее: используй 2 потока (для wrk), сохрани 400 открытых соединений в пуле и постоянно вызывай конечную точку GET в течение 30 секунд. Здесь я использую только два потока, так как wrk и тестируемая программа выполняются на одном и том же компьютере, и мне бы не хотелось, чтобы они конкурировали друг с другом по части доступных ресурсов, особенно CPU.

    Каждый веб-сервис тестировался отдельно и перезапускался после каждого выполнения.

    Ниже представлены лучшие из трех выполнений для каждой версии программы.


    Использование CPU при достижении конечной точки /hello


    Использование памяти при достижении конечной точки /hello


    Количество запросов в секунду при достижении конечной точки /hello


    Использование CPU при достижении конечной точки /greeting


    Использование памяти при достижении конечной точки /greeting


    Количество запросов в секунду при достижении конечной точки /greeting

    Эта конечная точка принимает параметр пути сегмента number>, возвращает число Фибоначчи и число ввода, сериализованное в формате JSON.

    Для этой конкретной конечной точки была выбрана реализация рекурсивным методом. Без сомнения, мне известно, что итеративный метод реализации дает гораздо лучшие результаты производительности и больше подходит для целей продакшена. Однако встречаются случаи, когда в коде продакшена целесообразнее использовать рекурсию (не обязательно для вычисления n-го числа Фибоначчи). В связи с этим я предпочел, чтобы реализация была активно вовлечена в распределение стека CPU.


    Использование CPU при достижении конечной точки /fibonacci


    Использование памяти при достижении конечной точки /fibonacci


    Количество запросов в секунду при достижении конечной точки /fibonacci

    Во время теста конечной точки Фибоначчи реализация Java была единственной, чье время ожидания истекло на 150 запросах, как показано в выводе wrk .


    Время ожидания


    Задержка для конечной точки /fibonacci

    Размер среды выполнения

    Чтобы имитировать реальное облачное приложение и избавиться от реплик вроде “Это работает на моем компьютере!”, был создан docker-образ для каждого из трех приложений.

    Источник для файлов Doker включен в репозиторий в папке соответствующей программы.

    Что касается версий приложения Go и Rust, они были статически скомпилированы. Это значит, что они не требуют наличия libc (glibc, musl и т. д.) в образе среды выполнения, а также им не нужен базовый образ с OS для выполнения. Поэтому я использовал Docker-образ scratch, который не выполняет операций и содержит исполняемый файл с нулевыми затратами ресурсов.

    В качестве условного обозначения для Docker-образа был использован /webservice. Размер образа для каждой из версий приложений Java, Go и Rust соответственно составляет 113 МБ, 8,68 МБ и 4,24 МБ.


    Итоговый размер Docker-образов

    Заключение


    Три языка в сравнении

    Прежде чем делать какие-либо выводы, мне бы хотелось обратить внимание на взаимосвязи (или их отсутствие) между этими тремя языками. Java и Go — языки с функцией сбора мусора, при этом Java компилируется методом АОТ в байт-код для JVM. При запуске приложения Java инициируется JIT-компилятор, чтобы по мере возможности оптимизировать байт-код путем компиляции его в машинный код для увеличения производительности приложения.

    Go и Rust компилируются в машинный код методом АОТ, и в дальнейшем никакой оптимизации в среде выполнения не происходит.

    Java и Go — языки с функцией сбора мусора и с побочным эффектом stop-the-world. Это значит, что при своем запуске сборщик мусора прекращает работу приложения, чистит память и по мере готовности возобновляет приложение с места его остановки. Функция stop-the-world необходима для работы большинства сборщиков мусора, но есть и реализации, не требующие ее.

    Язык Java был создан в далекие 90-е, и одним из его знаменитых лозунгов стал: “Написано однажды — работает везде”. В то время Java был передовой разработкой, так как рынок не отличался многообразием решений виртуализации. Сейчас же большинство CPU её поддерживают, что сводит на нет соблазн разработки с использованием языка только на том основании, что его код будет работать везде (в любом случае и на любых поддерживаемых платформах). Можно просто использовать Docker или другие решения, которые предлагают выгодную виртуализацию.

    В процессе тестирования версия приложения Java потребляла намного больше памяти, чем аналогичные версии Go и Rust. Результаты первых двух тестов показали, что Java использовал на 8000% больше памяти. Если бы речь шла о реальном приложении, то операционные расходы на приложение Java были бы выше.

    Первые два теста показали, что приложение Go использовало на 20% меньше CPU, чем Java, при этом обслуживая на 38% больше запросов. С другой стороны, версия Rust использовала на 57% меньше CPU, чем Go, обслуживая на 13% больше запросов.

    Третий тест намеренно активно задействовал CPU, и я был настроен выжать из него каждый бит. Go и Rust использовали на 1% больше CPU, чем Java. Если бы команды wrk не выполнялись на одном компьютере, то все три версии задействовали бы CPU на 100%. По показателям памяти Java потреблял на 2000% больше, чем Go и Rust. Java обслужил на 20% больше запросов, чем Go, в то время как Rust — на 15% больше запросов, чем Java.

    К моменту написания статьи язык программирования Java существует уже около 30 лет, в связи с чем найти на рынке разработчиков Java не составляет труда. С другой стороны, Go и Rust — относительно новые языки, и, естественно, что число их разработчиков меньше по сравнению с Java. Но надо сказать, что они стремительно набирают обороты и все чаще используются для новых проектов. Кроме того, существует много Go и Rust проектов, выполняемых в продакшене, так как они превосходят Java в эффективности с точки зрения потребляемых ресурсов.

    С точки зрения конкурентоспособности, на мой взгляд, Go прямой соперник для Java (и в целом языков JVM), но не для Rust. С другой стороны, Rust серьёзный конкурент для Java, Go, C, and C++.

    Учитывая их производительность, продолжу писать программы и в Go, и в Rust, но с большей долей вероятности — в Rust. Оба этих языка прекрасно подходят для веб-сервисов, CLI, разработки системных программ и т. д. Rust обладает фундаментальным преимуществом над Go. Он не является языком с функцией сборки мусора и спроектирован для безопасного написания кода, в отличие от C и C++. Go не совсем подходит для написания ядра ОС, тогда как Rust справляется с этим великолепно и может посоревноваться с C/C++, являющимися классическими и фактическими языками для написания ОС. Еще одна область, в которой Rust может составить конкуренцию C/C++, касается сферы встроенного ПО, но об этом мы поговорим в другой раз.

    Читайте также: