ПрограммированиеФорумОбщее

Unity: Причины утечки памяти и возможные решения (3 стр)

Страницы: 1 2 3
#30
4:49, 9 ноя 2014

alexzzzz
одно уг. А the forest тормозит посильнее калофдьюти и крайзисов

#31
9:17, 9 ноя 2014

FUNNY FACE
> Надо понимать, что внутренние ресурсы игровые, такие как текстуры, звуки итд -
> это не managed code.
шта?

#32
12:16, 9 ноя 2014

Вот только собрал UE4 под линукс запустил редактор и:
ShaderCompileWorker': double free or corruption (out): 0x000000000350a350 ***
ShaderCompileWorker': free(): invalid size: 0x0000000002997290 ***
Типа перестарались... ;)

#33
16:22, 9 ноя 2014

Feo
> FUNNY FACE
> > Надо понимать, что внутренние ресурсы игровые, такие как текстуры, звуки итд
> > это не managed code.
> шта?

Это не управляемые ресурсы, их надо освобождать. Вернее, это управляемые обёртки над неуправляемыми ресурсами. Если где-то есть

texture = new Texture2D();

значит, где-то должно быть

Destroy(texture);

Ещё есть метод Resources.UnloadUnusedAssets(), но он не так интуитивен.

#34
16:32, 9 ноя 2014

war_zes
> одно уг. А the forest тормозит посильнее калофдьюти и крайзисов
Первые два Кризиса я бросил, на третий вообще забил. Колофдьюти аналогично, даже не знаю, сколько их навыходило. Для меня это уг. А в KSP по статистике наиграно больше 300 часов.

#35
16:43, 9 ноя 2014

alexzzzz
Можно офф пруф, подтверждающий твои слова ?

#36
15:23, 10 ноя 2014

Igor Developer
> Можно офф пруф, подтверждающий твои слова ?
Я это считал само собой разумеющимся. В документации тема явно не раскрыта, но на форуме много вопросов и ответов про утекающие ресурсы.

Я не уверен, что у меня в голове 100% правильная картина того, как Unity работает с ресурсами, но попробую её сформулировать, а потом проверить экспериментально. Давно надо было.


1. Классы типа Mesh, Texture, ещё какие-то - это обёртки над неуправляемыми ресурсами. GC при всём желании не может удалить данные из памяти, например, видеокарты, он про неё ничего не знает.

2. GC умеет освобождать только управляемые ресурсы. Это везде так, что в Unity/Mono, что в Net. Для купирования проблемы существуют финализаторы - методы, которые автоматически вызываются сборщиком мусора перед удалением объектов. Если мы открыли, например, поток на запись в файл, ссылку обнулили, а поток перед этим не закрыли, то файл так и останется занятым. Если GC когда-нибудь решит удалить объект "поток", он перед этим вызовет его финализатор, который освободит файл. Это может произойти через минуту после обнуления всех ссылок на поток, может через полчаса, может в момент завершения работы программы, а может вообще не произойти - все финализаторы могут не успеть отработать до закрытия процесса.

3. Unity не пользуется финализаторами. Причина - финализаторы вызываются в отдельном потоке, а работать с ресурсами Unity разрешается только из главного. Таким образом, меши, текстуры и прочее надо как-то освобождать вручную.

4. Текущая сцена в Unity имеет внутреннюю коллекцию ссылок на все созданные во время её работы ресурсы. Прямого доступа к этой коллекции нет, но объекты в ней можно найти при помощи Object.FindObjectsOfType(...).

Также сцена хранит иерархию игровых объектов, их компоненты и ссылки из них на ресурсы.

5. Когда происходит вызов Resources.UnloadUnusedAssets, Unity удаляет те ресурсы, на которые нет ссылок из иерархии объектов и компонентов текущей сцены. Похоже на принцип работы сборщика мусора. Документация говорит, что статические поля компонентов тоже учитываются, но не ясно, играет ли какую-либо роль модификатор доступа public, private и прочие. Локальные переменные не учитываются, т.е. после

var texture = new Texture2D();
Resources.UnloadUnusedAssets();

только что созданная текстура должна уничтожиться.

bool a = ReferenceEquals(texture, null); // должно выдывать false, т.к. texture ссылается на управляемую обёртку над текстурой
bool b = texture == null; // благодаря перегруженному оператору сравнения должно выдавать true,
                          // т.к. реального ресурса, на который ссылается обёртка, больше не существует

6. Когда происходит закрытие сцены, входящие в иерархию объекты (кроме помеченных через Object.DontDestroyOnLoad) уничтожаются, а ресурсы, на которые они ссылаются, освобождаются.

Если какой-то скрипт за время работы сгенерировал три меша, но только один из них присвоен компоненту MeshFilter, то последний уничтожится автоматически, а два других, по идее, должны утечь. Их надо или уничтожать вручную через Object.Destroy(), или вызывать Resources.UnloadUnusedAssets().


---
Вот как-то так. Теперь пункты 4, 5 и 6 надо проверить экспериментально. Ещё надо проверить свойства MeshFilter.mesh и .sharedMesh, Renderer.material, .materials, .shaderMaterial, .sharedMaterials на предмет, в какие точно моменты при обращении к ним происходит неявное создание копии ресурса, а когда нет.

#37
21:46, 10 ноя 2014

Проверил:

Пункт 3.

Проверил рефлектором, что ни у Texture2D, ни Texture, ни Mesh, ни UnityEngine.Object нет финализаторов/деструкторов. Действительно нет.

Пункт 4.
> Текущая сцена в Unity имеет внутреннюю коллекцию ссылок на все созданные во время её работы ресурсы

Есть такое. В профайлере видно в разделе Memory -> Detailed -> Scene Memory. Метод Object.FindObjectsOfType эти объекты находит.

Пункт 5. Приколько всё.

var texture = new Texture2D();

- Если создать таким образом текстуру в методе Awake или OnEnable, то к вызову Start она уже будет уничтожена автоматически безо всяких Resources.UnloadUnusedAssets. Каким образом ― хз.

- Если создавать текстуру внутри Start, она так и будет висеть в памяти до вызова Resources.UnloadUnusedAssets или до смены сцены.

- Если сохранять ссылку на Texture2D не в локальную переменную, а в поле с любом модификатором доступа, или в свойство, или даже в свойство с типом object, то текстура не уничтожается по Resources.UnloadUnusedAssets, но уничтожится при смене сцены.

- Resources.UnloadUnusedAssets уничтожает объекты не сразу, а в промежутке между кадрами. Таким образом после

var texture = new Texture2D();
Resources.UnloadUnusedAssets();

текстура будет ещё жива, но в следующем кадре уже нет.

Пункт 6 неверен.
> Если какой-то скрипт за время работы сгенерировал три меша, но только один из них присвоен компоненту MeshFilter, то последний уничтожится автоматически, а два других, по идее, должны утечь.

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

#38
17:11, 11 ноя 2014

alexzzzz
То есть получается, что разработчик как бэ застрахован от того что при смене сцены утечки будут копится?
Получается что надо заморачиваться только в плане профилирования самой сцены?

#39
17:53, 11 ноя 2014

Относительно ресурсов Unity получается так, но лучше на эту страховку не рассчитывать и своевременно за собой убирать.

Но утечки можно ещё и самому себе устроить, например:

1. Экземпляр класса Manager генерирует какие-нибудь события, экземпляр класса Client подписался на получение этих событий.
2. Экземпляр класса Client больше не нужен, мы обнуляем все ссылки на него, ожидая, что GC его удалит.
3. Но экземпляр класса Manager всё ещё хранит ссылку на Client, соответственно, GC не может его удалить, а также не может удалить все объекты, на которые этот Client ссылается, а также объекты, на которые ссылаются те объекты, и т.д.

Или создали фоновый поток, передали туда какие-то данные для обработки, получили результат, а поток закрыть забыли (допустим, он продолжает работать в цикле), то в этом потоке тоже могут остаться ссылки на данные, которые уже не нужны, и GC не сможет их удалить.

#40
21:38, 11 ноя 2014

Все-таки Resources.UnloadUnusedAssets предназначен немного для другого. Для выгрузки из памяти ассетов, загруженных через Resources.Load. Ну и заодно в нем GC.Collect вызывается вроде, после прохода по всем ссылкам, поэтому такой эффект. Когда ты загружаешь что-то через Resources, ты загружаешь ассет. Это может быть Texture2d, Mesh и т.д. Дальше его можно инстанцировать (Instantiate) сколько угодно раз. Каждый инстанс хранит ссыль на ассет. Если в сцене есть хотя бы один инстанс, ассет не выгрузится даже через Resources.UnloadUnusedAssets. Инстанс удаляется через Destroy, ассет - только выгружается, попробуешь удалить ассет, он исчезнет из ресурсов и приложение будет крашить всегда. Я делаю акцент на выгрузке ассета потому, что его вообще-то можно использовать напрямую, без инстанса. Загрузил, к примеру, какой меш из ресурсов - и сразу в какой-нибудь MeshFilter его, без инстанса. И все работает. Но есть вероятность случайного удаления, поэтому так делать не принято.
А теперь веселая часть. Загрузил ты, например, в свою сцену аддитивно другую сцену (Application.LoadLevelAdditive). Потом решил удалить. Удаляешь го, который является парентом всей загруженной сцене, либо удаляешь пообъектно, каждый го, каждый меш, текстурку и т.д. - неважно: ассеты всех этих ресурсов останутся висеть в памяти. Даже после Resources.UnloadUnusedAssets. Ссылок нет (даж профайлер памяти пишет что количество ссылок 0), а они висят.. И ведь до ассетов этих не добраться, чтобы хоть выгрузить вручную через Resources.UnloadAsset, ты ведь сам их не загружал.. Лааадно. Теперь, допустим, у нас есть скрипт в сцене, в паблик переменную которого в едиторе дизайнер перетащил префаб. Все. Ассеты, связаные с этим префабом, вечно будут висеть в памяти приложения после запуска сцены (на счет смены сцен - хз). Хоть ты заудаляйся, занули все ссылки. Resources.UnloadUnusedAssets не поможет. Тоже самое с загрузкой ГО из папки Resources: ГО-то ты потом удалишь, а вот связанные с ним ассеты типа меша, мата и текстуры - никогда. Я из-за этого веселья запилил что-то типа своего менеджера ассетов с ручной загрузкой и подсчетом ссылок (плюс еще можно размазывать во времени подгрузку/выгрузку поассетно). С AssetBundle'ами таких проблем вроде нет: что загрузили, то и выгрузили. Проблема только в том что сам бандл дополнительно грузится в память до доставания из него ассетов. Ну и только один бандл может висеть в памяти, да. Еще надо помнить, что стримминга в юнити нет до 5ки. Даже все что "async" - безбожно лагает в самый ответственный момент. Хоть как-то может помочь, опять же, свой менеджер, загружающий все постепенно, следя за лагами.

П.С. Ловил все это еще в 4.3, изменилось ли что-то сейчас - не проверял пока

#41
23:09, 11 ноя 2014

Belfegnar
>Загрузил ты, например, в свою сцену аддитивно другую сцену (Application.LoadLevelAdditive). Потом решил удалить. Удаляешь го, который является парентом всей загруженной сцене, либо удаляешь пообъектно, каждый го, каждый меш, текстурку и т.д. - неважно: ассеты всех этих ресурсов останутся висеть в памяти. Даже после Resources.UnloadUnusedAssets.
Была похожая проблема, асинхронно подгружал куски уровня, сильно текла память. В профайлере выяснилось, что юнити не удалял за собой меш статического батчинга для каждой из загруженных сцен. Но Resources.UnloadUnusedAssets всё нормально выгружал, только лаг от него большой. Поэтому временно отключил статический батчинг.

#42
1:32, 13 ноя 2014

alexzzzz
> . Но экземпляр класса Manager всё ещё хранит ссылку на Client

weak reference ?

Страницы: 1 2 3
ПрограммированиеФорумОбщее

Тема в архиве.