2015-07-28 3 views
4

Я создал приложение с Enthought Traits, которое использует слишком много памяти. Я думаю, проблема вызвана уведомлениями об атрибутах:Использование памяти @on_trait_change vs _foo_changed()

Существует, по-видимому, фундаментальное различие в использовании памяти событий, пойманных @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)] 

При выполнении этого сценария с с = Foo, диспетчер задач Windows, показывает использование памяти для всего процесса питон 70MB, который остается постоянным для повышения п. Для c = FooDecorator процесс python использует 450 Мбайт, увеличиваясь для более высоких n.

Не могли бы вы объяснить мне это поведение?

EDIT: Может быть, я должен перефразировать: зачем кому-то выбирать FooDecorator над Foo?

EDIT 2: Я просто удалил python (x, y) 2.7.9 и установил новейшую версию купола с характеристиками 4.5.0. Теперь 450 МБ стали 750 МБ.

EDIT 3: Скомпилированные черты-4.6.0.dev0-py2.7-win-amd64. Результат такой же, как в EDIT 2. Поэтому, несмотря на всю правдоподобие, https://github.com/enthought/traits/pull/248/files, по-видимому, не является причиной.

+1

Относительно РЕДАКТИРОВАНИЯ 2: Исправлена ​​ошибка с установкой двух недель назад. Официального релиза еще не было. Если вы хотите установить край кровотечения, клонируйте репозиторий git на https://github.com/enthought/traits и установите с помощью 'python setup.py install' – pberkes

+0

Я могу подтвердить эту проблему. Кто-то открыл билет на GitHub для отслеживания проблемы: https://github.com/enthought/traits/issues/255 – pberkes

ответ

3

Что здесь происходит, так это то, что Traits имеет два разных способа обработки уведомлений: статические уведомители и динамические уведомления.

Статические уведомители (например, созданные с помощью специально названных методов _*_changed()) довольно легки: каждая черта экземпляра имеет список уведомлений о t, которые в основном являются функциями или методами с облегченной оболочкой.

Динамические оповещатели (например, те, которые создаются с on_trait_change() и extended trait name conventions как 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 

вы должны увидеть аналогичную производительность памяти для специально названных методов.

Чтобы ответить на перефразированный вопрос «зачем использовать on_trait_change», в FooDecorator вы можете написать один метод вместо двух, если ваш ответ на изменение списка или любых элементов в списке одинаковый. Это значительно упрощает отладку и обслуживание кода, и если вы не создаете тысячи этих объектов, дополнительное использование памяти незначительно.

Это становится еще более важным фактором, когда вы рассматриваете более сложные расширенные шаблоны имен признаков, где динамические слушатели автоматически обрабатывают изменения, которые в противном случае требовали бы значительного ручного (и подверженного ошибкам) ​​кода для подключения и удаления слушателей из промежуточных объектов и черты. Сила и простота этого подхода обычно перевешивают опасения по поводу использования памяти.

+0

Я цитирую [Руководство по стилям 4] (http://docs.enthought.com/traits/traits_user_manual/ notification.html): «.. Статически, путем украшения методов в классе с помощью декоратора on_trait_change(), чтобы указать, что они обрабатывают уведомление для определенных атрибутов». Из этого я предположил, что on_trait_change() приведет к статическому уведомителю, точно так же как специальное соглашение об именах. Может быть, руководство могло бы прояснить это? – HeinzKurt

+0

Да, вы правы, что руководство здесь немного запутывает. Я добавил вопрос о github: https://github.com/enthought/traits/issues/256 –

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' 

bar уведомитель будет вызываться для изменений признака a, его элементов, а также для изменения признака b в каждом из Bar пунктов. Расширенные имена могут быть довольно мощными.