如何清理Python中占用内存的冗余对象?国际象棋引擎内存过高求解
Python对象只有在引用计数归0时才会被回收,你手动del变量名、调用gc接口没有效果,说明这些实例还被其他上游对象持有引用,常见的持有场景包括搜索树节点存了Board引用、双向绑定形成循环引用、全局容器缓存了历史实例等。
先定位引用来源
直接用objgraph自带的回溯方法找持有Board实例的对象,运行以下代码就能输出所有引用Board的上游对象:import objgraph # 最多回溯5层调用链,输出所有持有Board引用的对象 objgraph.find_backrefs(objs=objgraph.by_type('Board'), max_depth=5)定位到引用来源后再针对性清理即可。
从根源减少Board实例创建
象棋引擎搜索走法时,绝大多数开发者都会每步生成新的Board副本,这是内存暴涨的核心原因。改成增量更新+状态回滚的模式:全程只初始化1个Board实例,递归搜索走法时直接更新Board的状态,递归返回上层前撤销这步的修改,完全不需要生成多余的Board实例,同时还能大幅提升搜索性能,是象棋引擎的标准优化方案。处理循环引用
如果你的Board、Move、Node类之间有双向引用(比如Board实例存了当前走法对应的Move对象,Move对象又存了所属的Board实例),会形成循环引用,要是还自定义了__del__方法,Python的gc完全无法回收这类对象。要么把双向引用改成weakref弱引用,要么直接取消双向绑定,只保留单向引用即可。额外优化手段
给Board、Move这类高频实例化的类添加__slots__属性,禁止实例动态添加属性,单实例内存占用可以降低30%以上,同时还能提升属性访问速度。搜索时不要全局缓存全部分支的节点对象,深度优先搜索退出当前分支时主动把分支持有的Board、Move、Node引用置为None,主动释放引用计数。
你之前删除Move实例没有效果是正常的:绝大多数Move实例都被Board或者Node对象持有,只要Board的引用没有释放,Move的引用计数就不会归0,解决了Board的引用问题后Move的数量会同步下降。
内容的提问来源于stack exchange,提问作者Jetdr

