2013-06-27 85 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 MB和作爲位圖數據大約3-3.5 MB。使用大圖輕鬆查看內存泄漏。

預期的結果,當你運行我的代碼:Python的過程週期吃掉存儲週期。當它消耗大約500 MB時,它崩潰了,但又開始消耗內存。

請給我一些建議如何解決這個問題。我很感激每一個幫助。謝謝。提前。

+0

首先,消費500MB真的是個問題嗎?對於這個問題,500MB只是虛擬內存還是物理/駐留內存? Python通常不會將內存返回給操作系統;它會在您稍後需要時繼續使用。這通常會讓事情變得更快 - 重複分配,釋放和重新分配幾十MB需要很長時間。另外,你在哪個平臺上?例如,在64位OS X上,大多數進程最終都會有數百MB的VM,而在32位Linux上,這種情況不太常見。 – abarnert

+0

我不知道它是物理還是VRAM。我對編程頗爲陌生,我不知道用什麼工具來檢查它。你能推薦一些嗎? 我使用命令行和taskmanager中的tasklist來跟蹤內存消耗。我的操作系統是Win7 x64。 所以你說,只要它有時會崩潰就不是問題?這將是一個相當緩解。 – bardosd

+0

TaskManager顯示物理和虛擬內存的單獨號碼,但我不記得它們的名稱。無論如何,如果您認爲可能存在實際問題,則必須先了解內存管理在Windows中的工作原理,然後才能進行調查。如果你沒有任何實際的問題,你只是擔心,不知道爲什麼,只是不要擔心。 – abarnert

回答

8

首先,你絕對沒有內存泄漏。如果它在接近500MB時「崩潰」並且從不穿過它,則它不可能泄漏。


而我的猜測是你根本沒有任何問題。

當Python的垃圾收集清理的東西了(當你用它在做的CPython通常會立即發生),所以一般不會實際釋放內存的操作系統。相反,它會保留它,以備日後需要時使用。這是有意的 - 除非你打亂了交換,重用內存比保持釋放和重新分配要快得多。

另外,如果是500MB的虛擬內存,那也沒有一個現代的64位平臺上。如果它沒有映射到物理/駐留內存(或者如果計算機閒置,則映射爲映射),但這不是問題;這只是操作系統與有效免費的資源很好。

更重要的是:是什麼讓你覺得有問題?是否有任何實際的症狀,或只是在程序管理器/活動監視器/頂/任何令你感到恐懼的東西? (如果是後者,請查看其他程序。在我的Mac上,目前有28個程序使用超過400MB的虛擬內存,我使用16GB中的11個,儘管少於3GB實際上是有線的,如果我說啓動邏輯,內存的收集速度將比邏輯可以使用的速度快;在此之前,爲什麼操作系統會浪費精力去取消內存映射(特別是當它無法確定某些進程不會去要求它不是以後使用)內存?


但如果有一個現實的問題,有兩種方法可以解決這個問題。


第一個竅門是在子進程中執行所有內存密集型操作,您可以終止並重新啓動以恢復臨時內存(例如,通過使用multiprocessing.Processconcurrent.futures.ProcessPoolExecutor)。

這通常會讓事情變慢而不是變快。當臨時內存大部分直接進入GUI時,這顯然不容易,因此必須在主進程中生存。


另一種選擇是找出內存的使用位置,而不是同時存放很多對象。基本上有兩個部分:

首先,在每個事件處理程序結束之前釋放所有可能的東西。這意味着在文件上調用close,或者將對象的所有引用設置爲None,在不可見的GUI對象上調用destroy,最重要的是不存儲對不需要的東西的引用。 (你真的需要在使用它之後保留PhotoImage嗎?如果是這樣,是否有任何方法可以按需加載圖像?)

接下來,請確保您沒有參考週期。在CPython中,只要沒有循環就立即清理垃圾 - 但是如果有的話,他們會坐下來,直到循環檢查程序運行。您可以使用gc module進行調查。一個非常快的事情是嘗試這種每隔一段時間:

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

如果您看到了巨大的下降,你有周期。您必須查看gc.getobjects()gc.garbage,或者附加回調,或者只是推測您的代碼以準確找到週期的位置。對於每一個,如果你真的不需要在兩個方向的參考,擺脫一個;如果你這樣做,將其中一個改爲weakref

+0

謝謝你的回答。我擔心,因爲我注意儘可能少用資源,但仍然只是種植和種植。現在我看到它是確定的。我也很感謝你的解決問題的提示。如果我有真正的記憶問題,他們會派上用場。 :) – bardosd

相關問題