Что лучше работает в Unity? MonoBehaviour, Jobs или Entities?
В начале 2023 года мы начали разработку новой версии фреймворка AppCore, позволяющий разрабатывать приложения без написания кода, используя лишь нодовый редактор. В новой версии нам предстояло добавить поддержку ECS в наш фреймворк. Еще тогда наш фреймворк был полностью зависим от Unity и нам предстояло разобраться, подойдет ли Entities для интеграции с AppCore или же нам предстоит использовать только Jobs для повышения скорости работы фреймворка.
В Unity существует несколько путей решения одной и той же задачи. Пару лет назад разработчики движка анонсировали D.O.T.S. – стек технологий, который призван ускорить работу игр на этом самом движке. Данный стек разрабатывается неравномерно и некоторые его части работают хорошо, а некоторые не очень хорошо.
Изначально у нас появилась потребность в том, чтобы изучить то, как работать с каждым из вариантов, найти их слабые и сильные стороны, а после сделать синтетический замер скорости и узнать, что же лучше.
Подготовка
Сделать игру - это хорошо. Однако мы решили, что за основу мы возьмем самый простой и доступный алгоритм - Boids. Он имеет набор правил, которые можно было бы удобно раздробить в будущем, а также он хорошо подходит для того, чтобы показать, насколько сильно может проседать производительность при росте количества сущностей. Код во всех трех реализациях был максимально одинаковый, дабы сделать замеры “как есть”.
Производительность работы алгоритма будем измерять следующим образом: главная сцена загружает сцену с алгоритмом, передает количество сущностей и через 30 секунд выгружает сцену, замеряя количество отрисованных кадров, после чего снова загружает сцену и передает другое количество сущностей. Конечно, это может дать свои погрешности, но при длине замера в 30 секунд они будут незначительны.
Всего 5 замеров на одну реализацию: 100, 1000, 5000, 10000, 20000 сущностей. Каждая реализация была собрана по отдельности, а тесты производился по несколько раз и были взяты средние показатели.
Важное уточнение: biods - это больше синтетический тест. Если делать полноценный проект, то значения могут отличаться и иногда даже значительно.
Сцена
На сцене для замеров не было ничего, кроме скрипта для управления сценами, источника света с отключенными тенями и камеры, которая отрисовывала только сплошной цвет и сущности. В качестве рендера использовался URP, так как он является наиболее популярным и универсальным рендером на данный момент.
Алгоритм
В интернете достаточно много реализаций, но нам необходимо было не просто сделать замеры скорости, но и разобраться в том, на сколько удобно и комфортно работать с тем или иным вариантом написания кода.
Реализация алгоритма Boids сама по себе очень проста - это по сути просто 3 или более правила, которые применяются к каждой сущности. Самое “тяжелое” правило - выравнивание скорости относительно друг друга. В нем был использован цикл в цикле, что является чем-то ужасным в программировании, но так даже интересней, потому что программисты не всегда пишут красивый и аккуратный код. Конечно, если избавиться от цикла в цикле, то результаты станут чуть более линейными.
Реализация алгоритма на MonoBehaviour
С самого начала появления игрового движка Unity практически весь код был завязан на использовании MonoBehaviour. Мы как программисты просто пишем компоненты, которые что-то выполняют, а после добавляем эти компоненты на любой объект. В наше время такой подход можно сравнить с YOLO программированием, что особенно хорошо подходит играм, которые разрабатываются одним человеком за короткий срок. Разработка в таком случае становится критически быстрой. Однако огромное количество компонентов неизбежно приведет к падению производительности и придется искать более оптимальные варианты, как например использование одного компонента MonoBehaviour на весь проект. Но даже при такой реализации производительность может быть не особо большой. Однако в данной статье удобство при написании с использованием MonoBehaviour мы будем считать эталонным.
Реализация алгоритма с Jobs System
Логичным шагом для повышения производительности стало бы использование Jobs System. Главная его особенность - возможность использования потоков. При этом его можно использовать и вместе с компилятором Burst, который может добавить еще немного производительности. Однако такой подход заставляет больше думать и самостоятельно управлять не только потоками, но и памятью. Подобное программирование можно сравнить с программированием на языке без сборщика мусора. Но как только проходит привыкание к новым типам и требованиям к построению кода, то единственной проблемой остается лишь ужасный вывод ошибок, который сообщает о проблеме очень расплывчато.
Тем не менее взамен некоторым трудностям мы получаем действительно ощутимый прирост производительности. При том что для нашего алгоритма можно каждое правило выделить в отдельный “джоб”.
Реализация алгоритма с Entities
D.O.T.S. состоит не только из Jobs System и Burst, но так же и из собственной реализации ECS - Entities, которая построена на архетипах и в сочетании с остальным стеком обещает какую-то нереальную производительность. Но мы для тестов будем использовать лишь малую часть - Entities и Burst. Мы решили не использовать Jobs, так как итог будет очевидным - результат будет или на 5-10% лучше или наравне с реализацией, использующей только Jobs+Burst.
При использовании ECS мы получаем примерно такую же возможность распределить наши правила на несколько систем, что очень удобно. Но взамен мы должны сделать много сторонних действий, которые кажутся неоправданными.
Во время проведения тестов в релиз вышла версия Entities 1.0. Тогда Entities не имел актуальной документации, некоторые вещи имели другие названия (по сравнению с Entities 0.50). Создавать тест было очень сложно. Сейчас есть уже Entities 1.2, который, по слухам, имеет адекватную документацию и более комфортен в работе.
Помимо того, что пакеты нужно было ставить вручную (они не доступны в пакетном менеджере напрямую), уживаться с некоторыми ограничениями и особенностями, так еще и стабильность данной ecs могла варьироваться от версии к версии. Дебаггинг так же сложнее, чем при использовании Jobs. А если вы использовали для написания кода не Rider или Visual Studio, то вам придется выбирать между этими двумя вариантами, так как Entities использует кодогенерацию, которая работает только с ReSharper.
Тесты на ПК
Для тестов использовалась следующая система:
OS: Windows 10 Pro
CPU: Intel Core i7-6800K 3.40GHz
GPU: NVIDIA GeForce GTX 1060 3GB
RAM: 2x8Gb DDR4 2666MHz
Тесты на мобильном устройстве
Для замеров на мобильном устройстве был использован достаточно бюджетный смартфон Redmi 9A.
Вывод
Если учесть логарифмическое построение диаграмм и тот факт, что у нас используется цикл в цикле, то мы получим весьма интересные и практически линейные результаты. Как мы можем заметить реализация с использованием Jobs System действительно очень производительная, стандартная реализация (на MonoBehaviour) оказалась самой медленной, а вот Entities расположилась между ними.
Казалось бы, если нам нужно чуть больше производительности, то стоит выбирать Entities? Можно было бы ответить положительно, но обратите внимание на то, как ведет себя Entities на Android. И это не случайно. Все дело в том, что версия 1.0 сломана и работает крайне нестабильно. В ходе тестов приложение могло закрыться с ошибкой, графика отрисовываться неправильно или вовсе не отрисовываться, а также текущая версия ecs отказывается работать с IL2CPP без дополнительных манипуляций, информацию о которых очень сложно найти.
В сравнении с эталонной реализацией (в плане удобства) на MonoBehaviour реализация на Entities выглядит очень нелогичной, сложной и многословной. Реализация на Jobs System также выглядит сложной, но данная сложность обоснована и при написании многопоточного кода не возникает ощущения того, что тут что-то не так.
Объективная сравнительная таблица
Стандартная реализация по прежнему является одним из лучших выборов при разработке, но из-за низкого порога входа разработчики могут создавать архитектуры, которые крайне сложно поддерживать. ECS стал популярен как раз за счет того, что при его использовании в первую очередь повышается качество кода, а архитектура проекта выстраивается чуть ли не самостоятельно. Однако реализация ECS от разработчиков движка выглядит как антипаттерн. Jobs же наоборот показали себя только с лучшей стороны.
Если вы заинтересованы в использовании ECS - рассмотрите альтернативные варианты от сторонних разработчиков. Если вам нужна реализация, которая такая же быстрая, как Entities+Jobs - рассмотрите Morpeh, который куда удобней и приятней в работе. Он поддерживает работу с Jobs, а его производительность ничуть не уступает производительности ecs от разработчиков движка.
Что же вошло в AppCore?
Сейчас, в январе 2024 года новая версия AppCore находится на стадии раннего тестирования, мы используем собственную архитектуру ECSA, которая легко может быть интегрирована с Jobs. Однако само ядро AppCore больше не зависит от Unity.
Ссылки
Видео о AppCore
Фреймворк
Адаптер для Юнити
Пример использования