[Перевод] Разработка критически важных алгоритмов, часть 3: Интеграция
- Проектирование
- Реализация
- Интеграция
Все хорошее когда-нибудь заканчивается, хотя этой серии постов это не касается. Хоть они скоро и закончатся, далеко не факт, что они хороши.
Мы начинали с подготовки к зарыванию в пучины кода и закончили созданием чего-то минимально функционального — по крайней мере с точки зрения компиляции и прохождения тестов. Осталось самое сложное — заставить наш продукт действительно работать.
Хочу предварить этот пост освобождением от ответственности: серебряной пули для интеграции вашего кода не существует (если бы она была, я бы давал консультации и разбогател). Я могу привести лишь рекомендации, которые позволят сделать этот процесс менее болезненным.
А эти процессы могут быть очень болезненными. Исходя из своего ограниченного опыта, я могу сказать следующее: когда инженеру впервые приходится связывать большой блок кода с другими большими блоками, он тратит на это много времени и нервов, некоторые даже бьются головой об стену.
Но в результате получается что-то, что работает, и это круто. Но, возможно, что еще более важно, вы прокачаетесь и станете немного лучше, сделав это в первый и самый худший раз. (Я бы сказал, что это похоже на написание академической статьи).
Интеграция другими словами
По сути, интеграция — это подключение, поиск проблем и их устранение. Иными словами, это длительный и неприятный процесс устранения неполадок.
Один из моих коллег недавно прислал хорошую статью о решении сложных проблем — я призываю вас взглянуть на нее.
Сам я могу предложить следующее:
- Осознайте цепочку событий в вашем процессе или алгоритме
- Разработайте тесты, чтобы определить, где в цепочке событий возникает проблема
- Решите проблему
- Повторите
Компьютеры работают последовательно. Когда вы начинаете работать с многопоточностью и переходите к низкому уровню сборки, все может стать неясно, порой порядок выполнения становится непонятным и начинается асинхронное безумие.
Тем не менее, на макроуровне алгоритмы в значительной степени последовательны (если вы следовали нашим предыдущим советам).
Поскольку наша проблема линейна, мы можем ее быстро локализовать (вспомните нашего дорогого друга — двоичный поиск), будь она на уровне ввода, вывода или где-то между ними.
Тестировать поток исполнения можно как простыми методами (расставлять везде операторы печати и проверять вывод), так и сложными (составлять специальные тестовые примеры поведения в соответствии с функциональной спецификацией).
В идеальном мире у нас всегда были бы идеальные спецификации и прекрасные тестовые примеры, которые соответствовали бы требованиям, но в реальном мире так бывает далеко не всегда. Спецификации могут быть неправильными или двусмысленными, интерфейсы API могут не поддаваться тестированию, и всегда существует насущная проблема целесообразности. Все нужно выпускать вовремя и платить за это.
Делайте все, что поможет протестировать ваш код. Я предпочитаю самыми простыми и быстрыми способами нахождения и воспроизведения ошибок.
Как только вы сможете воспроизвести проблему, исправить ее, как правило, не так уж и сложно. Логика такая же: разберитесь в последовательности исполнения, найдите проблемный раздел, и внимательно изучайте его, пока не поймете в чем дело. Если изучение кода не помогает, вы всегда можете сыграть роль модели машинного обучения и начать вводить пары входных данных с ожидаемыми результатами, пока вы у вас не появится интуиция в отношении этого фрагмента кода и не придет понимание где находится ошибка.
Тогда проблема будет решена.
На самом деле нет. Если только нам не повезет (или если мы хороши в написании кода!). Обычно одна проблема скрывает под собой другие, либо ваш код работает верно только в пределах данного ODD.
Именно так и можно вкратце описать процесс интеграции. Это не что иное, как переименованное устранение неполадок. По описанию кажется, что это долго, утомительно и болезненно, и процесс, безусловно, может быть таким. Тем не менее, все это можно упростить, и у меня есть несколько советов для этого.
Советы по снижению болезненности интеграции
Интеграционное тестирование
Сюрприз! Мы вернулись к тестированию.
Вообще говоря, интеграционные тесты — это когда несколько компонентов работают согласованно. Здесь мне нравится определять компонент как один узел.
В интеграционном тестировании вы хотите имитировать работу компонента или компонентов в более широкой системе. Для меня это означает подмену ввода (например, последовательности связанных траекторий и состояний транспортного средства для тестирования контроллера) и ожидание некоторого разумного результата. Ценой сложности и потери контроля вы получаете гораздо более мощный тест, который позволит более полно проверить ваш код и выявить проблемы.
Существует множество инструментов для интеграционного тестирования. В области ROS есть замечательный фреймворк launch_testing (разработанный нашим другом Питом, абсолютным мастером). Если это не в вашем вкусе, вы также можете запрограммировать интеграционные тесты в стандартной среде GTest.
Кстати, я отвечал за первые тесты в Apex.AI. Это была ужасная однострочная команда на bash, которая запускала исполняемый файл и проверяла, ничего ли не вылетает.
И это нормально (хотя есть способы сделать это лучше). Дымовое тестирование — это нормально, и любое тестирование, которое вы проводите, обычно избавляет вас потенциальной боли в будущем.
Возвращаемся к состояниям
Некоторые читатели могут вспомнить, что в предыдущем тексте я много говорил о состоянии объектов и о том, что понимание и защита этих состояний (на мой взгляд) могут стать ключом к написанию хороших классов.
Что ж, мы снова к этому вернулись.
Незначительные ошибки могут появляться, когда состояние или инварианты объекта не защищены должным образом. Основные выводы таковы:
- Помните об инвариантах
- Убедитесь, что вы их соблюдаете
- Минимизируйте и упрощайте состояние объекта
Первые два довольно просты (и могут быть реализованы с помощью контрактных механизмов), а последний представляет собой дополнение KISS. Если состояние меньше, значит и проблем с ним может возникнуть меньше.
Понять связаны ли ваши проблемы с состояниями позволит B2B-тестирование. Интересно, что именно эту форму тестирования рекомендуют в ISO26262, так что это еще одна веская причина для ее использования.
В качестве примера можно отметить, что одна из основных ошибок в реализации MPC была связана с состоянием и могла быть обнаружена при последовательном тестировании (back-to-back test). Все тесты в мире проверяют, что ваши алгоритмы работают только один раз, так что добавление последовательного теста — хороший способ убедиться, что вы охватываете случаи с i + 1.
Валидация ввода
Как следствие, для обеспечения целостности состояния вашего объекта между вызовами методов также важно внимательно следить за входными данными вашего алгоритма.
С точки зрения алгоритма входные данные обычно не проверяются, и из-за этого может возникать множество ошибок. В конечном итоге ответственность за обеспечение соответствия входных данных любым требованиям ложится на алгоритм.
Вообще говоря, эту проверку целесообразно выполнять перед выполнением любых вычислений и перед любым изменением состояния вашего объекта (помните, что самый быстрый код — это код, который никогда не запускается, а также помните о понятии строгих гарантий исключений).
Конкретный пример — ввод траектории для MPC, где проверяются две важные вещи: заголовок и периоды выборки.
Хотя комплексное представление заголовка позволяет легко избежать сингулярностей в пространстве SO (2), представление действительно только тогда, когда норма двух чисел равна единице. Для некоторых контроллеров заголовок даже не имеет значения, но для других, например MPC, это очень важно. На всякий случай все значения заголовков проверяются и, при необходимости, нормализуются.
Впрочем, не все контроллерам строго обязательна метка времени для опорной траектории. Некоторым нужен только набор инвариантных во времени точек в пространстве, представляющих путь. С MPC все иначе. Для него мы проверяем, что каждая из временных точек, связанных с каждой точкой траектории, достаточно близка к требуемой MPC дискретизации времени, а если нет, мы пытаемся исправить ее с помощью интерполяции.
Литература и прошлые наработки
Если вы следили за этими текстами, то, возможно, помните, что я ранее выступал за написание объемной проектной документации. Угадайте, что? Я снова говорю об этом!
Главной звездой в контексте интеграции является обзор литературы и документ о прошлых наработках.
Если у вас нет идей, как заставить что-то работать, всегда полезно сравнить нефункциональную реализацию с функциональной.
Например, можно вспомнить реализацию NDT в Autoware. Autoware столкнулась со значительными проблемами с центральным коридором на карте, которую мы использовали для тестирования. Сравнение функций показало, что (в интересах целесообразности) нам не хватало нескольких улучшений по сравнению с исходной версией Autoware.AI, а именно:
- Обеспечения хорошей обусловленности ковариационных матриц
- Линейного поиска More-Theunte
- Поиска по нескольким ячейкам
Мой дорогой (и разочарованный) коллега Юнус пошел дальше и попробовал каждый из этих вариантов, чтобы увидеть, какие из них улучшат производительность. Нам хватило обеспечения обусловленности ковариационных матриц и реализации поиска More-Theunte
Также у контроллера MPC были проблемы со стабильностью и джиттером. И нам опять не хватало интегрального воздействия в динамической модели, его использование зачастую повышает производительность в MPC-контроллерах.
Подводя итог этому разделу, могу дать следующие рекомендации:
- Сравните свой код с рабочими реализациями
- Изучите литературу: я люблю использовать поисковые запросы «прикладное использование <название алгоритма>»,»<название алгоритма> на практике» и «приложения <название алгоритма>».
Сократите цикл проверки
Теперь начинается печальная часть истории. К сожалению, у меня нет конкретных и полезных советов, которые могли бы довести до идеала ваш процесс интеграции. Вместо этого все, что у меня осталось, — это советы по облегчению боли.
Первый из этих советов (который хорошо согласуется с большинством современных практик разработки программного обеспечения) — сократить цикл процесса исправления-проверки-повторения. Для этого нужны:
- Процесс сборки одной кнопкой (или команда, для тех из вас, кто предпочитает командную строку)
- Тестирование одной кнопкой
В то время как сборка с помощью одной кнопки часто встречается в большинстве современных проектов, для эзотерических проблем, которые обнаруживаются при интеграции, тестирование с помощью одной кнопки использовать не так просто. Для этого очень пригодится новая система запуска ROS 2. Она может выполнять произвольные команды, и более того, они могут быть упорядочены. Это очень важно для автоматизации тестовых примеров.
Займитесь чем-то еще
Наконец, я могу посоветовать только собрать игрушки и отправиться домой.
Сделайте что-нибудь еще. В лучшем случае винтики вашего разума будут вращаться на заднем плане, и вас осенит, а в худшем случае вы, по крайней мере, сразу избавитесь от головной боли и дадите себе время расслабиться.
Что дальше?
Таким был процесс разработки NDT и MPC, и наш первый проход по V-образному циклу разработки. Мы начали с идеи, дополнили ее планом, создали продукт в соответствии с планом и убедились, что все работает. Теперь у нас есть два алгоритма, которые по меньшей мере делают то, что заявлено — локализуются по некоторой эталонной карте и следуют некоторой эталонной траектории.
Что дальше?
В целом, мы можем пойти двумя противоположными направлениями: сначала разобрать технический долг и повысить надежность, либо добавить функции (т.е. компромисс между доступностью и надежностью).
Технический долг — это факт жизни, особенно когда мы прилагаем все усилия, чтобы создать минимально жизнеспособный продукт. Всегда приходится разбираться с множеством таких долгов. В контексте NDT мы можем улучшить некоторые архитектурные аспекты, добавить больше интеграционных тестов и повысить надежность и устойчивость. Мой малоизвестный коллега Игорь уже начал работать над улучшением архитектуры. Для MPC мы аналогичным образом можем улучшить интеграцию, реализовать изящную обработку краевых случаев и повысить надежность и стабильность решателя.
На мой взгляд, технический долг может больно укусить, если не уделять ему внимания. Решение этой проблемы, вероятно, ускорит разработку в долгосрочной перспективе (при условии, что вы будете помнить о новых функциях и вариантах использования). Это немного похоже на задачу с зефиром, но я не уверен, может ли она быть лакмусовой бумажкой успеха проекта с открытым исходным кодом.
Новые функции всегда привлекательны. Новые функции всегда круче, интереснее и многое обещают. Но новые функции обычно сопровождаются новым техническим долгом, и тогда мы снова возвращаемся к тому же вопросу. Как и в случае с техническим долгом, никогда не бывает недостатка в интересных и новых функциях. И для NDT, и для MPC мы можем поиграть с решателем и проблемой оптимизации. Мы могли бы поиграть с D2D NDT или любой из многих разновидностей NDT, чтобы еще больше улучшить время выполнения и надежность нашего алгоритма локализации.
Однако в конечном итоге направление проекта определяется людьми, стоящими во главе, и людьми, оплачивающими счета. Я не принадлежу к числу этих людей, поэтому мое слово не имеет особого значения.
Как бы то ни было, я бы предпочел урегулировать технический долг, потому что, в конце концов, хорошо выполненная работа и хорошо созданный продукт — это уже хорошо. Думаю, что реализовав свои алгоритмы с открытым исходным кодом, мы проделали хорошую работу.
У вас будет возможность разрабатывать софт разного уровня, тестировать, запускать в производство и видеть в действии готовые автомобильные изделия, к созданию которых вы приложили руку.
В компании организован специальный испытательный центр, дающий возможность проводить исследования в области управления ДВС, в том числе и в составе автомобиля. Испытательная лаборатория включает моторные боксы, барабанные стенды, температурную и климатическую установки, вибрационный стенд, камеру соляного тумана, рентгеновскую установку и другое специализированное оборудование.
Если вам интересно попробовать свои силы в решении тех задач, которые у нас есть, пишите в личку.
Мы, пожалуй, самый сильный в России центр компетенций по разработке автомобильной электроники. Сейчас активно растем и открыли много вакансий (порядка 30, в том числе в регионах), таких как инженер-программист, инженер-конструктор, ведущий инженер-разработчик (DSP-программист) и др.
У нас много интересных задач от автопроизводителей и концернов, двигающих индустрию. Если хотите расти, как специалист, и учиться у лучших, будем рады видеть вас в нашей команде. Также мы готовы делиться экспертизой, самым важным что происходит в automotive. Задавайте нам любые вопросы, ответим, пообсуждаем.