Писал тут игру на конкурс, и в самый последний день заметил, что при перезапуске уровня происходит жестокая утечка памяти: создается новый экземпляр уровня, а старый не удаляется, хотя внешних ссылок на него не остается. Поразмыслив и погуглив, пришел к выводу, что причина — циклические ссылки у объектов с деструкторами (gc их собрать не может ввиду неопределенности порядка вызова деструкторов). Был несколько огорчен, ибо немалая часть логики была завязана именно на циклических ссылках.
Вот ссылка на ревизию, в которой я еще ничего не пытался исправлять, а вот мои потуги в исправлении. Основная суть в level.py, player.py и game.py, в последнем из которых на 84 строчке пересоздается и утекает уровень. В общей сложности в игре чуть более тысячи строк, но интерес представляют всего где-то 600.
Какие циклы увидел я:
- В game.py для space добавляются collision handler'ы — в level.space сохраняются ссылки на коллбэки, являющиеся методами level, т.е. фактически образуется цикл level → space → level. Вроде как решил переносом space в game.
- player хранил ссылку на game (т.е. game → player → game), чтобы иметь возможность после смерти образовать капли крови и затем вернуться на чекпоинт. Решил передачей game в качестве параметра хэндлеру on_die.
- Практически каждый класс в модуле level.py содержит ссылку на shape — объект типа pymunk.Shape. Но так как в collision handler передаются только ссылки на space и arbiter, из которых можно получить только физические объекты (тела и фигуры), мне нужно, чтобы фигура хранила еще некоторую дополнительную информацию (например, цвет у краски, next_level у выхода, id у триггера и прочее). Изначальное решение состояло в self.shape.sprite = self у объекта, что являлось явной циклической ссылкой. Решено частично, ибо при рисовании кляксы (level.py:365) мне нужно сразу обновить текстуру целевого объекта (т.е. нарисовать маску), а значит необходимо из фигуры иметь доступ к методу update_texture.
Из-за частичного решения третьего пункта и, вероятно, еще каких-то неучтенных деталей потребление памяти не уменьшилось.
Собственно, вопросы:
- Все ли циклические ссылки я нашел?
- Правильно ли я от них избавился, или это больше похоже на костыли, и ситуация требует пересмотра вообще всей архитектуры кода?
- Нужно ли было вообще от них избавляться, или все можно было решить гораздо проще?
- Как все-таки быть с обновлением текстуры, на которую был нарисован Blot?
- Еще интересно, почему повышается потребление только VIRT памяти, а RES стабильно остается на уровне 40 мб, но это не мешает игре со временем уходить в своп.
Если у кого-то есть время и желание, большая просьба помочь разобраться с этими вещами.