memory usage @on_trait_change vs _foo_changed()

时间:2015-07-28 16:56:50

标签: python decorator enthought traits

I did built an application with Enthought Traits, which is using too much memory. I think, the problem is caused by trait notifications:

There seems to be a fundamental difference in memory usage of events caught by @on_trait_change or by using the special naming convention (e.g. _foo_changed() ). I made a little example with two classes Foo and FooDecorator, which i assumed to show exactly the same behaviour. But they don't!

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)]

When running this script with c = Foo, Windows task manager shows a memory usage for the whole python process of 70MB, which stays constant for increasing n. For c = FooDecorator, the python process is using 450MB, increasing for higher n.

Can you please explain this behaviour to me?

EDIT: Maybe i should rephrase: Why would anyone choose FooDecorator over Foo?

EDIT 2: I just uninstalled python(x,y) 2.7.9 and installed the newest version of canopy with traits 4.5.0. Now the 450MB became 750MB.

EDIT 3: Compiled traits-4.6.0.dev0-py2.7-win-amd64 myself. The outcome is the same as in EDIT 2. So despite all plausibility https://github.com/enthought/traits/pull/248/files does not seem to be the cause.

2 个答案:

答案 0 :(得分:5)

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

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

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

例如,在这种情况下:

var nData = doc.XPathSelectElements("Orders/Order[@ID > 1]");
var root = new XElement("newWrap", 
                    new XElement("Orders", nData)
            );
var newDoc = new XDocument(root);
newDoc.Save("new_file.xml");

将调用class Bar(HasTraits): b = Str class FooDecorator(HasTraits): a = List(Bar) @on_trait_change('a.b') def bar(self): print 'change' 通知程序,以更改特征bar及其项目,以及更改每个a中的特征b项目。扩展名称可以非常强大。

答案 1 :(得分:3)

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

静态通知程序(例如由特别命名的_*_changed()方法创建的通知程序)相当轻量级:实例上的每个特征都有一个t上的通知符列表,它们基本上是函数或方法轻量级包装。

动态通知程序(例如使用on_trait_change()创建的动态通知程序和a[] HasTraits更强大且更灵活,但结果却更加重量级。除了它们创建的包装器对象之外,它们还创建了扩展特征名称和处理程序对象的解析表示,其中一些是转发a[]子类实例。

因此,即使对于像on_trait_change这样的简单表达式,也会创建相当数量的新Python对象,并且必须为每个实例上的每个on_trait_change侦听器分别创建这些对象。正确处理像实例特征一样的角落案例。相关代码位于:extended trait name conventions

根据报告的数字,您看到的内存使用量的大部分差异在于为每个实例和每个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 会发生短路,在这种情况下,它会生成静态特征通知程序而不是动态通知程序。所以,如果你要写一些类似的东西:

on_trait_change

你应该看到与特别命名的方法类似的内存性能。

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

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