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.
答案 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
中,如果您对列表中的更改或列表中的任何项目进行更改,则可以编写一种方法而不是两种方法相同。这使代码更容易调试和维护,如果您不创建数千个这些对象,那么额外的内存使用量可以忽略不计。
当您考虑更复杂的扩展特征名称模式时,这变得更加重要,其中动态侦听器自动处理更改,否则需要大量手动(且容易出错)的代码来连接和从中间对象中删除侦听器特征。这种方法的强大功能和简单性通常超过了对内存使用的担忧。