r.o*_*ook 5 python memory tkinter psutil
我想要一个快速而又脏的方法来获取一些文件名而无需在我的shell中输入,所以我有以下代码:
from tkinter.filedialog import askopenfile
file = askopenfile()
Run Code Online (Sandbox Code Playgroud)
现在,这一切工作正常,但它确实创造一个多余的tkinter,需要关闭GUI.我知道我可以这样做来压制它:
import tkinter as tk
tk.Tk().withdraw()
Run Code Online (Sandbox Code Playgroud)
但这并不意味着它没有装在背上.它只是意味着现在有一个Tk()我无法关闭/销毁的对象.
似乎每次我创建一个Tk(),无论我del或destroy()它,内存都没有被释放.见下文:
import tkinter as tk
import os, psutil
process = psutil.Process(os.getpid())
def mem(): print(f'{process.memory_info().rss:,}')
# initial memory usage
mem()
# 21,475,328
for i in range(20):
root.append(tk.Tk())
root[-1].destroy()
mem()
# 24,952,832
# 26,251,264
# ...
# 47,591,424
# 48,865,280
# try deleting the root instead
del root
mem()
# 50,819,072
Run Code Online (Sandbox Code Playgroud)
正如所看到的,蟒蛇甚至没有的每个实例后腾出的使用Tk()被破坏和root小号删除.然而,这不是其他对象的情况:
class Foo():
def __init__(self):
# create a list that takes up approximately the same size as a Tk() on average
self.lst = list(range(11500))
for i in range(20):
root.append(Foo())
del root[-1]
mem()
# 52,162,560
# 52,162,560
# ...
# 52,162,560
Run Code Online (Sandbox Code Playgroud)
所以我的问题是,为什么它Tk()与我之间有所区别Foo(),为什么不破坏/删除所Tk()创建的释放内存?
我错过了一些明显的东西吗?我的测试不足以证实我的怀疑吗?我在这里和谷歌搜索但发现答案很少.
编辑:以下是我尝试(并失败)的一些其他方法,其中包含评论中的建议:
# Force garbage collection
import gc
gc.collect()
# quit() method
root.quit()
# delete the entire tkinter reference
del tk
Run Code Online (Sandbox Code Playgroud)
这里存在三个问题,其中之一是tkinter你的错误,其中之一是你的错误,还有一个是你的行为不符合预期。
这三个问题是:
tkinter创建一个不可检测的引用循环作为注册其清理处理程序的一部分,只有通过显式调用才能破坏该destroy引用循环(如果您不这样做,则引用循环永远不会被清理,并且资源将永远保留)Tk即使你已经拿到了你的物品,你仍然会抓住destroy它们问题#1 意味着如果有机会恢复内存,您必须 destroy显式创建任何内容。Tk
问题 #2 意味着如果您希望内存可用于其他目的,则在创建新的 a 之前,您必须显式删除对 a 的任何引用Tk(在 ing 之后)。destroy在某些情况下,您还需要显式设置tk.NoDefaultRoot()以防止您创建的第一个对象Tk被缓存tkinter为默认根(也就是说,destroy对此类对象的显式调用将清除缓存的默认根,因此这不会在很多情况下都是一个问题)。
问题 #3 意味着您必须急切地删除引用,而不是等到程序结束才删除您的root list; 如果您等到最后才删除它,是的,内存将返回到堆,但不会返回操作系统,因此看起来您仍在使用所有内存。但这并不是一个真正的问题。如果操作系统需要 RAM,未使用的内存将被分页到磁盘(它通常在活动页面之前分页空闲页面),并且保留它可以提高大多数代码的性能。
具体来说.tkTkdestroyTk,即使您显式指定实例,实例的属性也不会被清除。您可以通过更改循环以消除对对象的最后一个引用来限制内存增长Tk,或者如果您只想释放低级 C 资源,请在新元素.tk之后显式取消链接**:destroyTk
# 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()
Run Code Online (Sandbox Code Playgroud)
根据我稍微修改的脚本的输出,显式清除引用可以清除底层资源:
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 ...
Run Code Online (Sandbox Code Playgroud)
事实上,第一个对象的内存永远不会被完全回收,这一事实并不奇怪。内存分配器很少会费心将内存实际返回给操作系统,除非分配量很大(大到足以触发模式切换,向操作系统发出与“小对象堆”分开管理的内存的独立请求)。否则,它们会维护一个不再使用且可以重用的空闲内存列表。
这里大约 6 MB 的“浪费”可能是在创建对象本身及其管理的对象树时涉及的一堆小分配Tk,虽然随后返回到堆以供重用,但不会返回到操作系统,直到程序退出(也就是说,如果堆的该部分不再使用,操作系统可能会优先将未使用的部分分页到磁盘(如果内存不足))。您可以通过注意到内存使用几乎立即稳定下来来了解此优化的帮助;新tk.Tk()对象只是重用与第一个对象相同的内存(缺乏完全稳定性可能是由于堆碎片导致需要少量额外分配)。
当您创建 的实例时Tk,您创建的不仅仅是一个小部件。您正在创建一个具有多个属性(嵌入式 tcl 解释器、小部件列表等)的对象。当您这样做时root.destroy(),您只是破坏了该对象拥有的部分数据。对象本身仍然存在并占用内存。由于您在列表中保留对该对象的引用,因此该对象永远不会被垃圾收集,因此内存会挂起。
当您使用 创建根窗口时root = tk.Tk(),您将得到一个对象 ( root)。如果您使用 vars 查看该对象的属性,您会看到以下内容:
>>> root = tk.Tk()
>>> vars(root)
{'children': {}, '_tkloaded': 1, 'master': None, '_tclCommands': ['tkerror', 'exit', '4463962184destroy'], 'tk': <_tkinter.tkapp object at 0x10a1d7f30>}
Run Code Online (Sandbox Code Playgroud)
当您调用 时root.destroy(),您只是销毁小部件本身(本质上是列表中的元素_tclCommands)。该物体的其他部分保持完好。
>>> root.destroy()
>>> vars(root)
{'children': {}, '_tkloaded': 1, 'master': None, '_tclCommands': None, 'tk': <_tkinter.tkapp object at 0x10a1d7f30>}
Run Code Online (Sandbox Code Playgroud)
请注意如何_tclCommands设置为None,但其余属性仍然占用内存。其中之一tk占用了大量永远不会被回收的内存。
要完全删除该对象,您需要将其删除。在您的情况下,您需要从列表中删除该项目,以便不再有对该对象的任何引用。然后,您可以等待垃圾收集器发挥其魔力,或者您可以显式调用垃圾收集器。
这可能不会回收 100% 的内存,但它应该可以让您非常接近。
话虽这么说,tkinter 并不是为了以这种方式使用而设计的。Tk基本期望是您在程序开始时创建一个实例,并保持该实例处于活动状态直到程序退出。
对于您的情况,我建议您在程序启动时创建根窗口一次,然后将其隐藏。然后,您可以askopenfile()在整个计划期间随意拨打电话。如果您想要更通用的功能,请创建一个函数,该函数在第一次调用时创建根窗口并缓存该窗口,以便只需创建一次。
| 归档时间: |
|
| 查看次数: |
528 次 |
| 最近记录: |