Jacky Liu's Blog

Vim 的 Python 接口的内存回收机制有问题 !!!!!

    ---- 续写 FileSystemExplorer 这个插件,现在写好的:
        1. 能刷新
        2. 能设置属性:
            a. 是否显示隐藏文件
            b. 显示基本输出(只有名称)还是扩展输出(包括大小,修改时间与访问时间)
            c. 设置根据名称 / 大小 / 修改时间 / 访问时间排序,设置正序或逆序。
        3. 能递归式打开节点(但是结果恐怖,后述。)
   
    ---- 几个要点记一下:

        1. 数据结构内部不要形成引用回路(reference cycle)。让文件节点同时保持对上级和下级节点的引用可以方便操作,但是对上级的引用要用 weakref 实现。如果形成引用回路就会产生没法析构的对象,以及内存泄漏。

        2. 怎样定义一个类似 list 的对象: 直接继承 list 类型不是个好主意,应该继承 Abstract Base Classes(ABC)里面的 MutableSequence,然后覆盖掉以下 “虚函数”: __init__(), __len__(), __getitem__(), __del__(), __setitem__(), insert()。

        3. 类 list 对象的读取操作与 list 形式一样,可以用 slicing,也可以用 comprehension。但是赋值不一样,不能直接
           

                self= 另一个sequence


        ,需要注意。

        4. 可以使用 del[:] 清除一个 类 list 对象,但是操作之前要先清除成员之间的引用关系。否则即使没有引用回路存在,Python 也不知道先清除哪个成员,结果又是一堆没法析构的对象。(对这一点还不是十分确定,有可能是太过谨慎了,回头写个程序验证一下。)

        5. 在函数的默认参数里不要使用可变值类型(mutable type)。比如:
           
            def my_function(arg=[]):
                pass

        这样是不对的,第二次调用时那个值就会变掉。应该这样:

            def my_function(arg=None):
                if arg is None: arg= []

    ---- 测试: 使用基本输出形式,显示隐藏文件,用 recursive 方式打开我的根目录(但是产生输出内容用的是线性处理方式),将近 49000 个节点(垃圾文件触目惊心),时间大概 8 秒。记得以前用 NerdTree 递归式打开 firefox 源文件的目录,也是几万个节点,花了两分钟以上。用扩展输出形式,多用 10 秒。我的电脑是 07 年的双核笔记本。所以 Python 接口的速度还是不错的,跟 VimScript 相比。

    ---- 最后一个大要点必须单独写:

                    Vim 的 Python 接口的内存回收机制有问题 !!!!!

    如上。虽然已经通过定义 __del__() 等方式确认所建立的 Python 数据对象都能被正确析构,但是内存占用还是一路彪升。用 recursive 方式打开一次根目录会增加几十 MB 内存,但是这些对象析构的时候内存却不减少。试着来回打开关闭了十几次,内存就到了 400 MB 以上,通过资源管理器来看,gvim 成了最耗内存的程序。

    后来把 Python 代码搬出来,改成一般的 Python 测试程序,通过 Shell 运行,没出现这种情况。递归式建立 49000 个节点会耗用 160MB 内存,但是后面无论怎样销毁再建立,内存都不再增加,可见内存回收在起作用。所以不是我代码写的有问题,有可能是 Vim 与 Python 的 garbage collector 通气不畅所致。

    [补记]:

    ---- 又想了个办法,在原来的 Vim 与 Python 混合代码里定义了一个测试命令,模拟其它所有内部操作但只是不往 Vim Buffer 做任何输出。通过此命令反复进行大量数据结构的建立与销毁操作,结果与上面的测试程序一样,内存占用是固定的,不会一直增加。所以问题出在 Python 接口上面。

    ---- 先不想这么多,内存问题绝对是我能力以外的事情。只要平时不会二到用 recursive 方式打开几万个节点的目录再关闭,再打开再关闭,再打开再关闭 ... 这个插件还是能用的。

    ---- 关于 Reference Cycle 的测试代码如下,如果两个对象互相硬指向对方的话,析构函数 __del__() 不会被调用,于是内存泄漏。

 

# -*- coding: utf-8 -*-

import weakref

class Child:
	
	def __init__(self, parent):
		
		# 这里切换使用软指向还是硬指向
		#	self._parent= parent	# XXX: 硬指向上级对象
		self._parent= weakref.ref(parent)	# XXX: 软指向上级对象
		
		print('Child.__init__() -- called !')

	def __del__(self):
		print('Child.__del__() -- called !')


class Parent:
	
	def __init__(self):
		self._child= None
		print('Parent.__init__() -- called !')

	def add_child(self, child):
		self._child= child			# XXX: 硬指向下级对象

	def __del__(self):
		print('Parent.__del__() -- called !')
		


parent= Parent()
child= Child(parent=parent)
parent.add_child(child=child)

del parent
del child

 




Host by is-Programmer.com | Power by Chito 1.3.3 beta | © 2007 LinuxGem | Design by Matthew "Agent Spork" McGee