2013-06-27 5 views
1

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

Когда изображения перезагружаются, все ссылки переносятся в новые данные изображения, так как они привязаны к той же переменной, к которой привязаны исходные данные изображения. Я попытался заставить сбор мусора использовать collect(), но это не помогло.

Я написал небольшой образец, чтобы продемонстрировать свою проблему.

from tkinter import Button, DISABLED, Frame, Label, NORMAL, Tk 
from PIL.Image import open 
from PIL.ImageTk import PhotoImage 

class App(Tk): 
    def __init__(self): 
     Tk.__init__(self) 
     self.text = Label(self, text = "Please check the memory usage. Then push button #1.") 
     self.text.pack() 
     self.btn = Button(text = "#1", command = lambda : self.buttonPushed(1)) 
     self.btn.pack() 

    def buttonPushed(self, n): 
     "Cycle to open the Tab module n times." 
     self.btn.configure(state = DISABLED) # disable to prevent paralell cycles 
     if n == 100: 
      self.text.configure(text = "Overwriting the bitmap with itself 100 times...\n\nCheck the memory usage!\n\nUI may seem to hang but it will finish soon.") 
      self.update_idletasks() 
     for i in range(n):  # creates the Tab frame whith the img, destroys it, then recreates them to overwrite the previous Frame and prevous img 
      b = Tab(self) 
      b.destroy() 
      if n == 100: 
       print(i+1,"percent of processing finished.") 
     if n == 1: 
      self.text.configure(text = "Please check the memory usage now.\nMost of the difference is caused by the bitmap opened.\nNow push button #100.") 
      self.btn.configure(text = "#100", command = lambda : self.buttonPushed(100)) 
     self.btn.configure(state = NORMAL) # starting cycles is enabled again  

class Tab(Frame): 
    """Creates a frame with a picture in it.""" 
    def __init__(self, master): 
     Frame.__init__(self, master = master) 
     self.a = PhotoImage(open("map.png")) # img opened, change this to a valid one to test it 
     self.b = Label(self, image = self.a) 
     self.b.pack()       # Label with img appears in Frame 
     self.pack()        # Frame appears 

if __name__ == '__main__': 
    a = App() 

Для того, чтобы запустите код, вам понадобится файл изображения PNG. Размеры моего map.png равны 1062 × 1062. Как PNG составляет 1,51 МБ, а в виде растровых данных - около 3-3,5 МБ. Используйте большое изображение, чтобы легко увидеть утечку памяти.

Ожидаемый результат при запуске моего кода: процесс python ест цикл памяти по циклу. Когда он потребляет около 500 МБ, он обрушивается, но начинает снова потреблять память.

Пожалуйста, дайте мне несколько советов, как решить эту проблему. Я благодарен за любую помощь. Спасибо. заранее.

+0

Во-первых, это действительно проблема потребления 500 МБ? Если это так, то 500 МБ просто виртуальная память или физическая/резидентная память? Python обычно не возвращает память ОС; он держит его для повторного использования, когда вам это нужно позже. И это обычно делает вещи более быстрыми - распределение, освобождение и перераспределение десятков МБ снова и снова занимает много времени. Кроме того, на какой платформе вы работаете? Например, в 64-битной ОС X большинство процессов заканчиваются сотнями МБ виртуальной машины, а на 32-битном Linux - гораздо реже. – abarnert

+0

Я не знаю, был ли это физический или VRAM. Я новичок в программировании, и я не знаю, как это проверить. Не могли бы вы порекомендовать кого-нибудь? Я использовал список задач из командной строки и taskmanager для отслеживания потребления памяти. Моя ОС - Win7 x64. Итак, вы говорите, что это не проблема, если она иногда рушится? Это было бы довольно облегчением. – bardosd

+0

TaskManager показывает отдельные номера для физической и виртуальной памяти, но я не могу точно помнить, что они называются. В любом случае, если вы считаете, что может возникнуть настоящая проблема, вам нужно узнать, как работает управление памятью в Windows, прежде чем вы сможете даже исследовать. Если у вас нет реальной проблемы, и вы просто волнуетесь и не знаете, почему, просто перестаньте беспокоиться. – abarnert

ответ

8

Во-первых, у вас определенно нет утечки памяти. Если он «рушится» всякий раз, когда он приближается к 500 МБ и никогда не пересекает его, он не может протекать.


И я предполагаю, что у вас нет никаких проблем.

Когда сборщик мусора Python очищает вещи (что обычно происходит сразу же, когда вы закончите с ним в CPython), он обычно не освобождает память ОС. Вместо этого он держит его на месте, если вам это понадобится позже. Это намеренно - если вы не обманываете своп, намного быстрее использовать память, чем освобождать и перераспределять ее.

Кроме того, если 500 МБ является виртуальной памятью, это ничего не значит на современной 64-битной платформе. Если он не отображается в физическую/резидентную память (или отображается, если компьютер простаивает, но быстро отбрасывается в противном случае), это не проблема; это просто ОС хорошо с ресурсами, которые эффективно свободны.

Что еще более важно: что заставляет вас думать, что есть проблема? Есть ли какой-либо фактический симптом или что-то в Менеджере программ/Мониторинг активности/вверх/что-то, что вас пугает? (Если последний, взгляните на другие программы. На моем Mac у меня есть 28 программ, работающих в настоящее время с использованием более 400 МБ виртуальной памяти, и я использую 11 из 16 ГБ, хотя менее 3 ГБ на самом деле подключен. Если я, скажем, запустил Logic, память будет собрана быстрее, чем Logic может ее использовать, до тех пор, почему бы OS-трафик тратится на размножение памяти (особенно если у него нет способа убедиться, что некоторые процессы не будут идти просить, что память не использовал позже)?


Но если это реальная проблема, есть два пути ее решения.


Первый трюк состоит в том, чтобы сделать все, что требуется для интенсивной памяти, в дочернем процессе, который можно убить и перезагрузить, чтобы восстановить временную память (например, используя multiprocessing.Process или concurrent.futures.ProcessPoolExecutor).

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

Другой вариант - выяснить, где используется память, а также не хранить так много объектов одновременно. В принципе, есть две части:

Сначала отпустите все возможное до конца каждого обработчика событий. Это означает, что вы вызываете close на файлы, либо del объектов или устанавливаете все ссылки на них на None, вызывая destroy объекты GUI, которые не видны и, прежде всего, не сохраняют ссылки на то, что вам не нужно. (Вам действительно нужно держать PhotoImage вокруг, когда вы его используете? Если да, то можете ли вы загрузить изображения по запросу?)

Затем убедитесь, что у вас нет ссылочных циклов. В CPython мусор очищается сразу же, пока нет циклов, но если есть, они сидят, пока не будет запущен контролер цикла. Вы можете использовать gc module, чтобы исследовать это. Одна очень быстрая задача - попробовать это так часто:

print(gc.get_count()) 
gc.collect() 
print(gc.get_count()) 

Если вы видите огромные капли, у вас есть циклы. Вам нужно будет заглянуть внутрь gc.getobjects() и gc.garbage, или приложите обратные вызовы или просто объясните свой код, чтобы узнать, где именно находятся циклы. Для каждого из них, если вам действительно не нужны ссылки в обоих направлениях, избавьтесь от одного; если вы это сделаете, измените один из них на weakref.

+0

Благодарим вас за ответ. Я волнуюсь, потому что я обращаю внимание на то, чтобы использовать как можно меньше ресурсов, но все же это просто выросло и выросло. Теперь я вижу, что все в порядке. Я благодарен за ваши намеки на решение проблем. Они пригодится, если у меня будут проблемы с реальной памятью. :) – bardosd