{"id":4012,"url":"\/distributions\/4012\/click?bit=1&hash=5b9cad3f989520ad358a2237d28d1f12ecdc50cb8452456f27fcbce716b2c8f0","title":"\u041f\u043e\u044f\u0432\u0438\u043b\u0441\u044f \u0438\u043d\u0441\u0442\u0440\u0443\u043c\u0435\u043d\u0442 \u0434\u043b\u044f \u0441\u0442\u0435\u0441\u043d\u0438\u0442\u0435\u043b\u044c\u043d\u044b\u0445 \u0440\u0438\u0435\u043b\u0442\u043e\u0440\u043e\u0432","buttonText":"","imageUuid":"","isPaidAndBannersEnabled":false}

Unity. Scriptable Object и память

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

Как столкнулся, в нашем проекте есть заготовленные куски уровней в виде prefab, в них и меши и логика и эффекты. При генерации уровень собирается из нескольких блоков. Так же есть Scriptable Object для хранения описания игрового уровня, какие блоки, что за уровень и.т.п., на менеджере всего этого дела висел список этих описаний уровней. В определенный момент столкнулся с тем что свободной оперативной памяти 0.

Для примера.

В проекте prefab, а в нем меши.
Так же в проекте Scriptable Object, в через инспектор накинута ссылка на prefab. Лежать может в любой папке.
А на сцене скрипт, которому через инспектор накинута ссылка на scriptable object.

Проблема первая.

В чем собственно проблема? Как только Unity видит переменную со ссылкой на scriptable object из проекта, он тянет его в память, а потом и все на что он ссылается, в том числе префабы блоков. А префабы блоков тянут конечно весь тот контент, что содержат. Для меня лично было не очевидно что ссылки на контейнеры данных, scriptable object, потянут за собой по цепочке пол проекта в память :) А у меня бюджет памяти не очень большой тем временем.

Решение. Можно загружать описания уровня по имени из Resources, разбить их вообще на два файла, в одном текстовая информация, для выбора уровней, а в другом связи с ассетами. Второй грузить только когда они нужны на сцене. Можно еще и с относительно новыми Adressables повозиться.

Проблема вторая.

Есть ещё одна менее очевидная проблема, если загрузить Scriptable Object в память при помощи:

preset = Resources.Load("ScriptablePreset") as ScriptableOne;

И потом удалить со сцены объект, который это делал (или вообще все объекты и даже сцену другую загрузить), то загруженный Scriptable Object будет висеть мертвым грузом в памяти и вместе с ним все на что ссылается он и далее по цепочке. Можно звать Garbage Collector, Resources.UnloadUnusedAssets, грузить другие сцены, все будет бестолку и сколько угодно времени, в памяти будет висеть то, на что никто уже не ссылается. Получалось что уровень я создал, потом удалил, но в памяти весь контент висит дальше, получается большой утечка памяти и через сколько то запусков на устройстве с сильно ограниченной памятью, типа Switch/Mobile игра крашнется.

Решение. Но как же быть? Выход то простой, нужно на OnDestruction явно обнулить переменную, где мы хранили указатель на Scriptable Object, GC сочтет это за руководство к действию.

private void OnDestroy(){ preset = null;}

Вот так, старые добрые деструкторы пиши. На всякий случай даже уточнил на форуме Unity не баг ли это, нет, "так и задумано". В profiler / profile analyzer | memory profiler такие ситуации отследить не очень то просто, у ассетов будет просто не указано кто его удерживает в памяти, т.к. обьект удален, и становится непонятно почему его сборщик мусора не ловит.

p.s. Все тестирование использования памяти было выполнено в development билдах с подключением профайлера, как на ПК, так и на консоли.

Интересно, только я такое проморгал в своем проекте?)

0
39 комментариев
Написать комментарий...
Alec

Не понял, почему первая проблема - это проблема. Подтянули в память объект с кучей референсов, юнити стандартно (!) для себя загружает объекты по цепочкам ссылок в память. Если хочется сохранить такую архитектуру, но без загрузки всего-всего, то правильно указали в ответе вам,  лучше в Addressables и смотреть. А в сторону Resources не смотреть)

Ответить
Развернуть ветку
shked

Проблема с точки зрения подхода к инструментарию движка. Даже самими Unity.

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

Не без помощи активного и пытливого комьюнити выяснилось, что ресурсы - та еще головная боль если так посмотреть.

Теперь же сами Unity признали это и пишут, что ресурсы - это плохо, не используйте. Что с одной стороны хорошо.

Но до кучи в список плохих практик, как я в этом же посте и выяснил, залетели и ассет бандлы, ровно с той же формулировкой - не используйте. Почему? А почему раньше можно было использовать?

И вот такой подход у них к мануалам много где наблюдается.
Чего только стоят новичковые туториалы, где место действия - Update()

Ответить
Развернуть ветку
Alec

По туториалам и Update(), кстати, не соглашусь скорее. Новичку полезно давать информацию по частям с увеличением сложности, и если поначалу в update() проще показать работу какой-то логики, то и прекрасно. Иначе он завязнет, например, в условных делегатах и ивентах, не дойдя до самой темы урока.

Ответить
Развернуть ветку
Alec
 Но до кучи в список плохих практик, как я в этом же посте и выяснил, залетели и ассет бандлы, ровно с той же формулировкой - не используйте. Почему? А почему раньше можно было использовать?

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

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

Ответить
Развернуть ветку
Andrey Apanasik

До Addressables уже давно существовали ассет бандлы. Unity уже несколько лет не советует Resources использовать.

Ответить
Развернуть ветку
WS SAWER

Addressables - как замена ресурсов, это костыль, что будет много хуже во многих случаях. Ну и пользоваться им на много сложнее, чем ресурсами или бандлами. По сути, это просто обёртка над бандлами с системой наполнения бандлов, где последние собираются Избыточно. И вот эта избыточность не даёт полноценно работать с памятью, хотя в целом профит есть.
Хлам создаёт каждый из этих инструментов, но в последние годы юнитеки не решают проблемы, а плодят)

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

PS: По вашим словам достаточно папку Resources переместить на уровень выше, сделать равнозначным с Assets. Ну и Editor тоже выбивается.

Ответить
Развернуть ветку
Anton
Автор
 Не понял, почему первая проблема - это проблема.

Яж говорю, в первом случае мне показалось что ссылка на scriptable object не потянет за собой в память префабы, на которые у него ссылки, а с ними префабы на которые ссылки у префаба, а с ними всее меши, которые лежат в префабе... И мне кажется для многих это не очевидно, особенно тех кто только начнет что то делать)

Ответить
Развернуть ветку
saber-nyan
 preset = null

Вот вам и автоматическое управление памятью с GC...

Ответить
Развернуть ветку
Андрей Иванов

Дело не в этом. Он же там объясняет. Что есть в юнити  есть обертки вокруг объектов. Дестроем ты удаляешь объект, а обертка остается. preset ссылается на обертку(при этом из-за перегруженного оператора "==" сравнение с null будет истиной), таким образом движок делается юзер фриндли, чтобы не падало все дальнейшее выполнение. Поэтому GC видит ссылку(а ты нет:)).
Ты можешь после Destroy вывести в лог результат:
bool result = (object)preset == null;
и увидеть, что он скажет - false.
Хотя при простом сравнении с null, вернет true.

Или например, когда ты делаешь GetComponent<> для компонента, которого на самом деле нет сейчас на объекте, то создается фейковый null объект (под него выделяется память), но за то, у тебя всё не падает дальше с концами.

Ответить
Развернуть ветку
Игродел

Так Статик объекты GC не удаляет, откуда ему знать, что уже не надо?

Ответить
Развернуть ветку
Anton
Автор

так preset не объявлен как static, это обычная переменная

Ответить
Развернуть ветку
Dmitriy Zaytsev

Вот это да, не знал, но подозревал. Про первый пункт, обычно храню в scriptable script только описание уровней и настройки, а сам уровень отдельным файлом, который загружается во время загрузки уровня. Наверно поэтому удавалось избежать такой проблемы.

Ответить
Развернуть ветку
Anton
Автор

Да, как то так, но все равно стоит пристально следить за тем кто на что ссылается, особенно крупное, и удалять самому, чтобы не утекало)

Ответить
Развернуть ветку
shked

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

И через прямые ссылки не надо контент тянуть, он также навечно в памяти застрянет.
Лучше через бандлы работать, либо через Adressables (но я про них пока мало знаю).

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

Ответить
Развернуть ветку
Anton
Автор

Да, сейчас по докам если смотреть, то вообще получается вот что

Ответить
Развернуть ветку
shked
Resources

Все так.

AssetBundle

Стоп что, а с ними-то теперь что не так?

Или они так ненавязчиво продвигают addresables?

Ответить
Развернуть ветку
Anton
Автор

Да 

Ответить
Развернуть ветку
shked

Ну впрочем, это до тех пор, пока они не придумают новый супер эффективный способ, а потом скажут, что старый был говно. ( ͡° ͜ʖ ͡°)

Ответить
Развернуть ветку
shked

Ну впрочем, это до тех пор, пока они не придумают новый супер эффективный способ, а потом скажут, что старый был говно. ( ͡° ͜ʖ ͡°)

Ответить
Развернуть ветку
Евгений Онянов

С чего это из Resources всё тянется в память? Всё тянется в билд, но не в оперативку. 

Ответить
Развернуть ветку
shked
Ответить
Развернуть ветку
Евгений Онянов

Так ты либо Resources используешь, либо ассет бандлы. Зачем всё сразу то?

Ответить
Развернуть ветку
shked

Ну вот так

Ответить
Развернуть ветку
Andrey Apanasik

Пост в ленте теряется. Думаю, неплохо бы было какую-нибудь из пикч в превью поставить.

Ответить
Развернуть ветку
Anton
Автор

Если только такую =)

Ответить
Развернуть ветку
Andrey Apanasik
Ответить
Развернуть ветку
Аккаунт удален

Комментарий недоступен

Ответить
Развернуть ветку
Dmitriy Zaytsev

У меня на них почти весь контент игр строится, еще добавить сюда Odin inspector и вообще круто.

Ответить
Развернуть ветку
A Hideo Kojima Game

Про обнуление референса не знал, но то, что юнити тянет вообще всё по референсам узнал, когда начинал работать с Addressables. В итоге теперь всё жирное храню в них или в ассетбандлах

Ответить
Развернуть ветку
FlyTroll

это же в эдиторе только?  У меня проект сейчас больше 16гб озу выжирает

Ответить
Развернуть ветку
Anton
Автор

Нет, все тестировалось в билдах.

Ответить
Развернуть ветку
Игродел

А решение то простое, документацию почитать:)

Ответить
Развернуть ветку
Anton
Автор

С первым пунктом да, а вот второй какой то не очевидный

Ответить
Развернуть ветку
Влад Обосратов

Хм, а таой вопрос задам - если я делаю Instantiate SO, то есть ли вариант его потом переименовать?

Ответить
Развернуть ветку
Anton
Автор

Scriptable Object же не для этого, он ассетом лежит в проекте ещё до запуска 🤔 Если речь про то, чтобы его создавать через editor-скрипты, не в сцене, то да, можно переименовать..

Ответить
Развернуть ветку
Влад Обосратов

Вот смотри, у меня есть поле СО в которое при старте instatiate СО, второй рисунок. Вот как его можно переименовать, чтобы убрать из названия clone?

Ответить
Развернуть ветку
Anton
Автор

Clone же только на том что создано во рантайме, зачем его вообще переименовывать? Надеюсь ты его потом на сцене по имени не ищешь) Вообще можно через .name же имя поменять, в сцене.

Ответить
Развернуть ветку
Влад Обосратов

Нет конечно, что за глупости, я же написал что у меня есть поле под СО, зачем мне его по имени искать? Просто интересно, есть ли такая возможность, т.к. сам не нашёл.

Ответить
Развернуть ветку
Stormtrooper SHFourTwoNineOneThree

У СОшника же есть свойство name, его не пробовал редачить?

Ответить
Развернуть ветку
Читать все 39 комментариев
null