Оптимизация SqlAlchemy для объектных моделей только для чтения - PullRequest
9 голосов
/ 24 февраля 2010

У меня сложная сеть объектов, порождаемых из базы данных sqlite с использованием сопоставлений sqlalchemy ORM. У меня довольно много глубоко вложенных:

for parent in owner.collection: 
    for child in parent.collection: 
        for foo in child.collection: 
            do lots of calcs with foo.property 

Мое профилирование показывает, что в этом случае инструментарий sqlalchemy отнимает много времени.

Дело в том, что я никогда не изменяю объектную модель (сопоставленные свойства) во время выполнения, поэтому, когда они загружены, мне НЕ НУЖНЫ ни инструментарий, ни вообще какие-либо накладные расходы на sqlalchemy. После долгих исследований я думаю, что мне, возможно, придется клонировать набор «чистого питона» из моих уже загруженных «инструментальных объектов», но это было бы больно.

Производительность здесь очень важна (это симулятор), поэтому, возможно, лучше написать эти слои как расширения C с использованием sqlite api напрямую. Есть мысли?

Ответы [ 3 ]

9 голосов
/ 24 февраля 2010

Если вы ссылаетесь на один атрибут одного экземпляра много раз, простой прием - сохранить его в локальной переменной.

Если вы хотите создать дешевый чистый клон Python, поделитесь объектом dict с исходным объектом:

class CheapClone(object):
    def __init__(self, original):
        self.__dict__ = original.__dict__

Создание такой копии обходится примерно в половину инструментального доступа к атрибутам, а поиск атрибутов выполняется так же быстро, как обычно.

Может также быть способ заставить маппер создавать экземпляры неинструментированного класса вместо инструментированного. Если бы у меня было время, я мог бы взглянуть, насколько глубоко укоренилось предположение, что заполненные экземпляры того же типа, что и инструментированный класс.


Нашел быстрый и грязный способ, который, кажется, хоть как-то работает на 0.5.8 и 0.6. Не проверял его с наследованием или другими функциями, которые могут плохо взаимодействовать. Кроме того, это касается некоторых непубличных API, поэтому остерегайтесь поломок при смене версий.

from sqlalchemy.orm.attributes import ClassManager, instrumentation_registry

class ReadonlyClassManager(ClassManager):
    """Enables configuring a mapper to return instances of uninstrumented 
    classes instead. To use add a readonly_type attribute referencing the
    desired class to use instead of the instrumented one."""
    def __init__(self, class_):
        ClassManager.__init__(self, class_)
        self.readonly_version = getattr(class_, 'readonly_type', None)
        if self.readonly_version:
            # default instantiation logic doesn't know to install finders
            # for our alternate class
            instrumentation_registry._dict_finders[self.readonly_version] = self.dict_getter()
            instrumentation_registry._state_finders[self.readonly_version] = self.state_getter()

    def new_instance(self, state=None):
        if self.readonly_version:
            instance = self.readonly_version.__new__(self.readonly_version)
            self.setup_instance(instance, state)
            return instance
        return ClassManager.new_instance(self, state)

Base = declarative_base()
Base.__sa_instrumentation_manager__ = ReadonlyClassManager

Пример использования:

class ReadonlyFoo(object):
    pass

class Foo(Base, ReadonlyFoo):
    __tablename__ = 'foo'
    id = Column(Integer, primary_key=True)
    name = Column(String(32))

    readonly_type = ReadonlyFoo

assert type(session.query(Foo).first()) is ReadonlyFoo
0 голосов
/ 24 февраля 2010

Вы должны иметь возможность отключить отложенную загрузку для рассматриваемых отношений, и sqlalchemy извлечет их все в одном запросе.

0 голосов
/ 24 февраля 2010

Попробуйте использовать один запрос с JOIN вместо петель Python.

...