bar*_*osd 2 python memory-leaks tkinter python-3.x pillow
我目前正在用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()
Run Code Online (Sandbox Code Playgroud)
要运行上面的代码,您需要一个PNG图像文件.我的map.png的尺寸是1062×1062.作为PNG,它是1.51 MB,作为位图数据,它是大约3-3.5 MB.使用大图像可以轻松查看内存泄漏.
运行我的代码时的预期结果:python的进程逐周期地占用内存.当它消耗大约500 MB时,它会崩溃,但会再次开始耗尽内存.
请给我一些如何解决这个问题的建议.我很感激每一个帮助.谢谢.提前.
首先,你肯定没有内存泄漏.如果它在接近500MB并且从未穿过它时"崩溃",它就不可能泄漏.
而我的猜测是你根本没有任何问题.
当Python的垃圾收集器清理时(通常在CPython中完成它时会立即发生),它通常不会将内存释放到操作系统.相反,它会保留它,以防您以后需要它.这是故意的 - 除非你是颠倒交换,重用内存比保持释放和重新分配它要快得多.
此外,如果500MB是虚拟内存,那么现代64位平台上什么都不是.如果它没有映射到物理/驻留内存(或者如果计算机处于空闲状态但是很快就会被映射,则会映射),这不是问题; 它只是操作系统很好的资源,实际上是免费的.
更重要的是:是什么让你觉得有问题?是否有任何实际症状,或只是程序管理器/活动监视器/顶部/什么吓到你?(如果是后者,请看看其他程序.在我的Mac上,我有28个程序当前使用超过400MB的虚拟内存运行,我使用16GB中的11个,即使少于3GB是实际上是有线的.如果我说,启动Logic,内存的收集速度会比Logic可以使用的更快;直到那时,为什么操作系统会浪费工作来取消内存(特别是当它无法确定某些进程不会去问那个以后没用过的记忆吗?
但如果是一个真正的问题,有两种方法可以解决这个问题.
第一个技巧是在子进程中执行所有内存密集型操作,您可以终止并重新启动以恢复临时内存(例如,通过使用multiprocessing.Process或concurrent.futures.ProcessPoolExecutor).
这通常会使事情变慢而不是更快.当临时存储器主要进入GUI时,显然不容易做到,因此必须存在于主进程中.
另一种选择是弄清楚内存的使用位置,而不是同时保留这么多对象.基本上,这有两个部分:
首先,在每个事件处理程序结束之前释放所有可能的东西.这意味着调用close文件,无论是del对象还是设置对它们的所有引用None,调用destroy不可见的GUI对象,最重要的是,不存储对不需要的东西的引用.(你真的需要PhotoImage在使用它之后保留它吗?如果你这样做,有没有办法可以按需加载图像?)
接下来,确保没有参考周期.在CPython中,只要没有循环就立即清理垃圾 - 但如果有,它们会一直坐着,直到循环检查器运行.您可以使用该gc模块来调查此问题.一个非常快速的事情是经常尝试这个:
print(gc.get_count())
gc.collect()
print(gc.get_count())
Run Code Online (Sandbox Code Playgroud)
如果你看到巨大的下降,你就有了周期.你必须往里gc.getobjects()并gc.garbage,或附加回调,或正当理由约你的代码,找到确切位置在循环中.对于每一个,如果你真的不需要两个方向的参考,摆脱一个; 如果你这样做,将其中一个改成一个weakref.
| 归档时间: |
|
| 查看次数: |
1804 次 |
| 最近记录: |