内存使用量@on_trait_change vs _foo_changed()

Hei*_*urt 4 python decorator traits enthought

我确实用Enthought Traits构建了一个应用程序,它使用了太多的内存.我想,问题是由特质通知引起的:

@on_trait_change或使用特殊命名约定(例如_foo_changed())捕获的事件的内存使用情况似乎存在根本差异.我用两个类Foo和FooDecorator做了一个小例子,我假设它表现出完全相同的行为.但他们没有!

from traits.api import *

class Foo(HasTraits):
    a = List(Int)

    def _a_changed(self):
        pass

    def _a_items_changed(self):
        pass

class FooDecorator(HasTraits):
    a = List(Int)

    @on_trait_change('a[]')
    def bar(self):
        pass

if __name__ == '__main__':
    n = 100000
    c = FooDecorator
    a = [c() for i in range(n)]
Run Code Online (Sandbox Code Playgroud)

当使用c = Foo运行此脚本时,Windows任务管理器显示整个python进程的内存使用量为70MB,这对于增加n保持不变.对于c = FooDecorator,python进程使用450MB,增加n更高.

你能告诉我这个行为吗?

编辑:也许我应该改写:为什么有人会选择FooDecorator而不是Foo?

编辑2:我刚刚卸载了python(x,y)2.7.9并安装了最新版本的canopy 4.5.0.现在450MB变成了750MB.

编辑3:编译特征-4.6.0.dev0-py2.7-win-amd64我自己.结果与编辑2中的结果相同.因此,尽管所有合理性,https://github.com/enthought/traits/pull/248/files似乎并不是原因.

pbe*_*kes 5

我相信你看到最近修复的内存泄漏的影响:https: //github.com/enthought/traits/pull/248/files

至于为什么会使用装饰器,在这个特定的例子中,两个版本实际上是等价的.

通常,装饰器更灵活:您可以提供要监听的特征列表,并且可以使用扩展名称表示法,如下所述:http: //docs.enthought.com/traits/traits_user_manual/notification.html #semantics

例如,在这种情况下:

class Bar(HasTraits):
    b = Str

class FooDecorator(HasTraits):
    a = List(Bar)

    @on_trait_change('a.b')
    def bar(self):
        print 'change'
Run Code Online (Sandbox Code Playgroud)

bar通知将被要求改变的特点a,它的项目,并为特质的变化b在每个的Bar项目.扩展名称可以非常强大.


小智 4

这里发生的事情是 Traits 有两种不同的处理通知的方式:静态通知程序和动态通知程序。

静态通知器(例如由专门命名的_*_changed()方法创建的通知器)相当轻量级:实例上的每个特征都有一个关于 t 的通知器列表,这些通知器基本上是带有轻量级包装器的函数或方法。

动态通知器(例如使用on_trait_change()扩展特征名称约定创建的通知器明显a[]更加强大和灵活,但因此它们的重量要大得多。特别是,除了它们创建的包装器对象之外,它们还创建扩展特征名称和处理程序对象的解析表示,其中一些又是HasTraits子类实例。

因此,即使对于像这样的简单表达式,a[]也会创建相当数量的新 Python 对象,并且必须为每个on_trait_change实例上的每个侦听器分别创建这些对象,以正确处理实例特征等极端情况。相关代码在这里:https://github.com/enthought/traits/blob/master/traits/has_traits.py#L2330

根据报告的数字,您看到的内存使用情况的大部分差异在于为每个实例和每个on_trait_change装饰器创建此动态侦听器基础结构。

on_trait_change值得注意的是,如果您使用简单的特征名称,则会出现短路,在这种情况下,它会生成静态特征通知程序而不是动态通知程序。因此,如果您要写类似以下内容:

class FooSimpleDecorator(HasTraits):
    a = List(Int)

    @on_trait_change('a')
    def a_updated(self):
        pass

    @on_trait_change('a_items')
    def a_items_updated(self):
        pass
Run Code Online (Sandbox Code Playgroud)

您应该会看到与专门命名的方法类似的内存性能。

要回答有关“为什么使用”的重新表述的问题on_trait_changeFooDecorator如果您对列表或列表中任何项目的更改的响应相同,则可以编写一种方法而不是两种方法。这使得代码更容易调试和维护,如果您没有创建数千个这样的对象,那么额外的内存使用量可以忽略不计。

当您考虑更复杂的扩展特征名称模式时,这变得更加重要,其中动态侦听器会自动处理更改,否则需要大量手动(且容易出错)代码来从中间对象和特征中连接和删除侦听器。这种方法的强大功能和简单性通常超过了对内存使用的担忧。