Как стать автором
Обновить

Golang для Embedded Linux

Время на прочтение8 мин
Количество просмотров12K

При разработке очередной платформы перед командой АТОЛ встал вопрос выбора языка программирования/стека технологий/железа/фреймворка для создания решений. Железо было выбрано на базе относительно недорогой Linux-платформы STM32MP153/512MB DDR3/8GB eMMC. Эта платформа имеет на несколько порядков больше ресурсов, чем используемые в нашей основной массе решений LPC1768/LPC1778/LPC4078/STM32F207. 100% наработок кода компании для устройств были написаны на C/C++, однако прогресс не стоит на месте, и периодически необходимо актуализировать инструменты и технологии разработки, особенно с учетом новых аппаратных возможностей. Из статьи станет ясно, как мы дошли до жизни такой и почему выбрали Golang для создания очередного набора решений.

Выбор стека технологий важен для всех компаний, которые занимаются разработкой железа и перерастают крошечные embedded контроллеры на Cortex M0/M3/M4/M7. Обычно команды при переходе на новую платформу выбирают одно из двух решений: стараются сделать новую версию системы на новом железе/технологиях/архитектуре, превращая решение в нестабильный долгострой, или наоборот — вносят минимальное количество изменений, но иногда вместо совокупности положительных черт разных подходов получают совокупность отрицательных.

В статье исследованы особенности различных языков программирования/технологий (Java, Python, C/C++, Rust, Golang), их плюсы и минусы, сформулированы критерии выбора и представлен выбор команды АТОЛ.

Для анализа использован метод SWOT-анализа. В качестве источников данных — информация сайтов фреймворков. Помимо этого, косвенная информация о боли и страданиях разработчиков получена на Stackoverflow, и часть субъективных выводов сделана на основе моего экспертного мнения за более чем 30-летний опыт программирования.

Почему к C/C++ решили добавить язык высокого уровня?

Итак, мы переехали на новое «железо», теперь у нас есть Linux, ресурсов на три порядка больше и целая куча легаси-кода на C/C++, в которых можно напилить нужные уровни абстракции/упаковать в приложение/библиотеку и начать жить по-новому.

Однако у такого подхода есть недостатки против применения языков высокого уровня:

  • Высокая трудоемкость при реализации новой функциональности, особенно если нужны высокоуровневые сущности (веб-сервер, работа с «хитрыми» базами данных, реализация брокера сообщений и прочее). Теоретически это решается созданием зависимости от third-party библиотек и подключением монструозных библиотек, но в любом случае этот код тяжелее читать/сопровождать/писать/отлаживать/профилировать по сравнению с языками высокого уровня.

  • Разработка и стабилизация функциональности на C/C++ занимает в разы больше времени, чем аналогичные действия на языках высокого уровня (Python, Golang, Java, C# и прочие), так как с указателями существует много взаимодополняющих способов отстрелить себе ноги, типа memory corrupt/memory leak/null pointer operations и прочих «радостей», которые можно улучшить при использовании новых стандартов/умных указателей, но не избавиться от них совсем.

  • Порог вхождения новых разработчиков выше, с учетом наличия различных стандартов С++, использования множественного наследования, шаблонов, сторонних библиотек boost/qt и прочих, чем для приложения с аналогичной функциональностью на языках высокого уровня (при создании разумной архитектуры).

  • Получается заметно больше кода, особенно при создании кроссплатформенных решений.

При всех указанных минусах, отказ от C/C++ для нас невозможен по двум причинам:

  • большое количество legacy-кода, наработанного за 20 лет существования компании;

  • мы разработчики оборудования, поэтому C/C++ в любом случае останется при разработке библиотек низкого уровня, написании модулей ядра, модификации низкоуровневых библиотек Linux/Android и прочих ОС, которые могут быть натянуты на новое железо.

Соответственно, нам нужно было выбрать язык/технологию, который дополнит C/C++.

Почему не С#, Ruby, JavaScript, TypeScript, Dart или Julia?

Ruby, JavaScript, TypeScript, Dart, Julia и прочие хайповые языки/технологии, имеющие по индексам TIOBE рейтинг менее 1%. Даже не спрашивайте, почему не они. Будем считать, что они просто не подходят на роль кроссплатформенного языка общего назначения под Embedded Linux/desktop/cloud с целью создания различных системных и веб-сервисов:) На чем создавать кроссплатформенные GUI-приложения — это отдельная большая история, которая выходит за рамки данной статьи.

C#. Он мне нравится как язык/фреймворк для создания Windows-приложений. Я писал на нем лет 10 «боевые» приложения, и до сих пор «грешу», если нужно быстро накидать GUI-приложение «для себя». Но у него есть, на мой взгляд, несколько фатальных недостатков:

  • Фактически он привязывает разработчика к среде разработки Microsoft Visual Studio (если хоть раз попробовал, то слезть почти невозможно), сборочные машины будут «виндовые», что сразу накладывает особенности/ограничения на ci/devops, который в других случаях красиво контейнеризируется в «легонькие» Linux-докеры.

  • Имеется сильная зависимость от .NET Framework, а он за последние годы стал сильно «пухленький» по сравнению с первыми версиями 1.1/2.0, которые умещались в какие-то жалкие 13-20 МБ. Это сильно осложняет его применение на устройствах с ограниченными ресурсами.

Возможно, какой-нибудь Universal Windows Platform (UWP) впоследствии вырастет до полноценного кроссплатформенного решения Embedded Linux, десктоп (Linux/Windows/MacOS) и облачных сервисов, но я пока в production такого не видел. Кроме того, поработав длительное время и создавая многопоточные кроссплатформенные сервисы Golang и C#, я однозначно делаю выбор в сторону Golang. Таким образом, C# отсеяли.

Наши альтернативы: Rust, Python, Golang, Java

Целевой ОС для разработки будем считать Linux, однако впоследствии при установке более мощного железа (STM32MP157, NXP серии i.MX и прочие) возможно появление Android, поэтому код должен собираться и туда. Огромным плюсом я считаю возможность собираться под десктоп (для создания эмуляторов устройств и переноса общей логики на выделенную машину в случае работы нескольких одинаковых устройств в сети), а также под облачные сервисы (при отсутствии инфраструктуры у юзера и выносе части логики на нашу или арендуемую клиентом облачную платформу).

В качестве языков высокого уровня будем рассматривать следующие альтернативы: Rust, Python, Golang, Java.

Есть красивые глубокие исследования по low-level производительности Rust, Python, Golang, Java, например, по микросервисам. Есть показательный benchmark разработанного веб-сервиса с задержкой (Latency) в миллисекундах на шкале слева при доверительной вероятности 99% и общей пропускной способности в запросах в секунду (RPS) на одинаковом железе.

Python

По большому счету, Python имеет одни из самых высоких задержек при создании системных сервисов и самую низкую производительность при создании pure python кода. То есть самые ответственные операции все равно придется писать на C/C++. Но я не считаю язык Python языком общего назначения, на мой взгляд, он хорош в боевом применении, только если надо писать облачные сервисы (web, AI), заниматься внутренней автоматизацией или создавать open-source код, хотя и имеет крайне низкий порог вхождения.

Если отойти от указанных применений влево-вправо, то начинаются разного рода приключения:

  • Важно, но не смертельно — в Python элементарно набиваются зависимости, которые быстро раздувают приложение в 50-100 МБ, если пытаться создать распространяемое кроссплатформенное решение.

  • Фатально — создание платного ПО, распространяемого по подписке или лицензиям, требует неприемлемых затрат на защиту кода, а иногда выдвигают доп. требования аппаратной защиты (USB-ключи, etc.). Нормальных сторонних обфускаторов тоже нет, а мы — коммерческая компания.

  • Фатально — при попытке создавать код системных приложений под Embedded Linux получаем жутко непредсказуемое потребление ОЗУ. Хочу, чтобы можно было нормально инкапсулировать логику в отдельные приложения, а не коверкать архитектуру из-за особенностей технологии, отказывая, например, в доступе к БД всему, кроме одного микросервиса, через который нужно пускать в БД остальные, так как работа с БД — это плюс пара десятков МБ ОЗУ, и для пары десятков микросервисов в Embedded Linux потеряем половину ОЗУ.

Можно поразвлекаться с PyPy, и грести его особенности потом в боевых применениях (так как для железа будет PyPy, а в облаке будет Python, и поведение может различаться). Кроме того, в нескольких предыдущих проектах мне неоднократно приходилось бороться с GIL, которого нет в других языках (но когда-нибудь это уже не будет проблемой). В итоге Python тоже отсеяли.

Java

С Java ситуация чуть лучше с точки зрения производительности, но не задержек. С первыми двумя минусами Python у Java получше, из-за JRE (входит в minimum requirements для платформы) и обфускаторов коммерческих решений — вагон.

Но один фатальный момент сохраняется:

  • Высокие требования к Flash, ОЗУ, а также вычислительной мощности из-за наличия «жирненького» JRE. Если бы у нас был только Android, то ситуация была бы немного лучше, но у нас cross-platform, да еще имеется Embedded Linux в скромной конфигурации.

Однако ответственные вещи можно переписывать на C/C++ или использовать низкоуровневые оптимизированные библиотеки, а в некоторых тестах и встроенные средства в Java ведут себя неплохо. Но подход постоянно подкладывать низкоуровневый код лишает преимуществ высокоуровневого языка, и фатальные проблемы никуда не деваются.

Rust vs Golang

Rust. В сети периодически возникают идеи отказа от C/C++ в пользу Rust (ссылка 1,  ссылка 2), но мы все прекрасно понимаем, что поделить бассейн на «писающих» и «не писающих» не получится, так как весь код вокруг остается на C/C++ и его придется сопровождать/развивать AS-IS, поэтому применение C/C++ для нас неизбежно. Однако можно добавить один высокоуровневый язык для понижения сложности сопровождения системы, снижения стоимости масштабирования/новой функциональности. Пару языков добавлять не хочется, чтобы не разводить зоопарк и не повышать сильно порог вхождения в разработку.

В итоге основная битва развернулась между Rust (рассматриваем как «язык высокого уровня») и Golang. Глядя на код, на одном и другом языке после C/C++ ничего смертельного не видно. Глядя на плюсы и минусы языков/технологий, я предвзято склонялся к Golang. Поэтому сделал SWOT-анализ для случая, если выберем Golang вместо Rust, чтобы себя проверить. Общие черты типа «memory safety», «быстрый», «компилируемый» в таблице не отражены, показаны только различия:

SWOT-анализ, если выбрать Golang вместо Rust

Сильное

Слабое

   Меньше вероятность проблем при кроссплатформенной разработке из-за более широкой популярности (в два раза по индексу TIOBE)

    Отличная концепция многопоточности (goroutines) и обмена сообщений (channel)

   Много встроенных полезных инструментов (тестирование, управление пакетами), как и в Rust, но дополнительно есть статический анализ, design-time анализ в средах разработки и профайлер

   Намного более быстрая сборка (кто часами ждал сборки на C/C++ оценит:) )

    В Golang синтаксис близок к C, и порог вхождения ниже, чем в Rust (steep learning curve линк 1, линк 2), что позволяет легче писать и сопровождать код, но из-за этого нет «zero cost abstractions», которые есть у Rust

    Не получим такого хорошего детерминированного поведения кода, как у Rust

     В большинстве тестов Golang проигрывает Rust, хоть и не так сильно, как другие высокоуровневые языки

    Golang использует «garbage collector» (больше потребление ОЗУ) против модели «lifetimes» у Rust, то есть это не язык системного программиста

   Golang внутри имеет промежуточные слои абстракции

Перспективы

Подводные камни

   Верим больше в технологии развиваемые гигантом Google, что не «помрет», как другие, так как активно используется в массовых решениях (Google Chrome, Google Earth, YouTube, Kubernetes, Docker, GitHub)

   Time to market фич/продуктов на Golang будет ниже, чем у Rust

  Возможно, будем упираться в недетерминированное поведение, повышенный расход ОЗУ/CPU, и это чаще придется переписывать на C/C++ или использовать сторонние оптимизированные библиотеки

Если бы надо было выбрать только один язык, то, возможно, я бы и склонился к Rust, но так как надо выбрать язык/технологию, который дополнит C/C++, то слабые стороны Golang теряют вес. Если использовать последние стандарты C++ и умные указатели, то также можно улучшить ситуацию и понизить необходимость в Rust.  Кроме того, для компании сейчас важно минимизировать время time to market: вместо стабилизации нового продукта месяцами хочется стабилизировать его неделями. Поэтому выбор пал на Golang.

При экспериментах с Golang на конкретном выбранном микроконтроллере STM32MP1 стало ясно, что у нас нет удаленной отладки (Delve remote debugging) из-за 32-bit arm архитектуры. Все новые контроллеры уже 64-bit, поэтому Google, видимо, не особо «напрягается», хотя уже есть x86 и 32-bit mips. Видимо, придется эту задачу решать первой, когда будем подходить к промышленному применению.

Понятно, что впереди маячит Go 2, где наконец-то порешают долгожданный вопрос с обработкой ошибок, и очевидно, что мы столкнемся с местами, где Rust подошел бы больше, чем Golang, и их придется написать на C/C++. Но, как гласит старая русская пословица, «багов бояться — код не писать».

Спасибо, что дочитали до конца :)

Теги:
Хабы:
Всего голосов 17: ↑11 и ↓6+6
Комментарии50

Публикации

Истории

Работа

Программист C++
109 вакансий
Rust разработчик
8 вакансий
Go разработчик
123 вакансии
Программист С
37 вакансий
QT разработчик
4 вакансии
Java разработчик
373 вакансии

Ближайшие события

15 – 16 ноября
IT-конференция Merge Skolkovo
Москва
22 – 24 ноября
Хакатон «AgroCode Hack Genetics'24»
Онлайн
28 ноября
Конференция «TechRec: ITHR CAMPUS»
МоскваОнлайн
25 – 26 апреля
IT-конференция Merge Tatarstan 2025
Казань