Здесь есть три проблемы, одна из которых - ошибка tkinter
, одна из которых ваша, а другая ведет себя как задумано.
Три проблемы:
tkinter
создает необнаружимый опорный цикл как часть регистрации своих обработчиков очистки, который прерывается только явным вызовом destroy
(если вы не сделаете этого, опорный цикл будет никогда очищены, и ресурсы сохраняются навсегда) - Вы держите свои
Tk
объекты даже после того, как вы destroy
их - Куча маленьких объектов редко, если вообще когда-либо,возвращается в ОС до завершения программы (память сохраняется для будущих распределений)
Проблема № 1 означает, что вы должны destroy
любой Tk
, который вы создаете явно, если таместь вероятность восстановления памяти.
Проблема № 2 означает, что вы должны явно избавиться от любой ссылки на Tk
(после destroy
его использования), прежде чем создавать новую, если вам нужна памятьбыть доступным для других Пуrposes.В некоторых случаях вы также можете явно установить tk.NoDefaultRoot()
, чтобы предотвратить кэширование первых Tk
, которые вы создаете, на tkinter
в качестве корневого каталога по умолчанию (при этом явные вызовы destroy
для такого объекта будуточистите кешированный корневой каталог по умолчанию, так что во многих случаях это не будет проблемой).
Проблема № 3 означает, что вы должны избавиться от ссылок с нетерпением, а не ждать до конца программы, чтобыудалите root
list
;если вы подождете до конца, чтобы удалить его, да, память будет возвращена в кучу, но не в ОС, поэтому будет выглядеть, как будто вы все еще используете ее.Это не настоящая проблема, хотя;неиспользуемая память будет выгружена на диск, если операционная система нуждается в оперативной памяти (обычно она распределяет пустые страницы перед активными), и ее сохранение повышает производительность большей части кода.
В частности, похоже, что атрибут .tk
экземпляров Tk
не очищается, даже если вы явно destroy
Tk
instancde .Вы можете ограничить рост памяти, изменив цикл, чтобы избавиться от последней ссылки на объект Tk
, или, если вы просто хотите освободить ресурсы низкого уровня C, явно отсоедините .tk
после destroy
, используя новый Tk
element **:
# Not necessary, but avoids caching any Tk as a root when you don't want it
tk.NoDefaultRoot()
root = [] # Missing in your original code, but I'm assuming it was a plain list
for i in range(20):
root.append(tk.Tk())
root[-1].destroy()
# Either drop the reference to the `Tk` completely:
root[-1] = None
# or just drop the reference to its C level worker object
root[-1].tk = None
# Optionally, call gc.collect() here to forcibly reclaim memory faster
# otherwise you're likely to see memory usage grow by a few KB as uncleaned
# cycles aren't reclaimed in time so we see phantom leaks (that would
# eventually be cleaned)
mem()
Явная очистка ссылки позволяет очищать базовые ресурсы на основе вывода моего слегка измененного сценария:
12,152,832
17,539,072
17,924,096 # At this point, the original code was above 18.8M bytes
17,965,056
17,965,056 # At this point, the original code was above 21.7M bytes
... remains unchanged until end of program if gc.collect() called regularly ...
Тот факт, чтопамять никогда не восстанавливается полностью для первого объекта, это не удивительно.Распределители памяти редко удосуживаются фактически вернуть память операционной системе, если только выделение не было огромным (достаточно большим, чтобы вызвать переключение режима, которое делает независимый запрос к ОС для памяти, которая управляется отдельно от "маленькой"куча объектов ").В противном случае они сохраняют свободный список памяти, который больше не используется и может быть использован повторно.
~ 6 МБ «мусора» здесь, вероятно, были кучей небольших выделений, связанных с созданием объекта Tk
Само по себе и дерево объектов, которым оно управляет, которое, хотя впоследствии и возвращается в кучу для повторного использования, не будет возвращено ОС до тех пор, пока программа не выйдет (при этом, если эта часть кучи больше никогда не будет использоваться, ОС может преимущественновыгрузите неиспользуемые части на диск, если на диске не хватает памяти).Вы можете увидеть, как эта оптимизация помогла, заметив, что использование памяти стабилизируется почти сразу;новые объекты tk.Tk()
просто повторно используют ту же память, что и первая (отсутствие полной стабильности, вероятно, связано с фрагментацией кучи, что приводит к необходимости небольших дополнительных выделений).