2010-02-23 12 views
8

Mam złożoną sieć obiektów tworzonych z bazy danych sqlite za pomocą mapowań SOlalchemy ORM. Mam sporo głęboko zagnieżdżone:Optymalizacje SqlAlchemy dla modeli obiektów tylko do odczytu

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

Moja profilowanie pokazuje mi, że oprzyrządowanie sqlalchemy bierze dużo czasu w tym przypadku zastosowania.

Chodzi o to: Nigdy nie zmieniam modelu obiektu (zmapowanych właściwości) w czasie wykonywania, więc po ich załadowaniu NIE POTRZEBUJĘ oprzyrządowania ani w ogóle żadnego narzutu sqlalchemy. Po wielu badaniach myślę, że będę musiał sklonować zestaw "czystego Pythona" z moich już załadowanych "instrumentowanych obiektów", ale to byłby ból.

Wydajność jest tutaj bardzo ważna (jest to symulator), więc być może najlepiej byłoby napisać te warstwy jako rozszerzenia C przy użyciu aplikacji sqlite api. jakieś pomysły?

Odpowiedz

7

Jeśli odwołujesz się do pojedynczego atrybutu pojedynczej instancji wiele razy, prostą sztuczką jest zapisanie jej w zmiennej lokalnej.

Jeśli chcesz sposobem na stworzenie tanich czystych klonów Pythonie udostępnić obiekt dict z oryginalnego obiektu:

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

Tworzenie kopii takiego kosztuje około połowę oprzyrządowanego dostępu atrybut i atrybut wyszukiwań są szybko jak zwykle.

Może istnieć sposób, aby mapper tworzył instancje niezabezpieczonej klasy zamiast instrumentowanej. Jeśli mam trochę czasu, mógłbym rzucić okiem na to, jak głęboko zakorzenione jest założenie, że zaludnione instancje są tego samego typu co oprzyrządowana klasa.


Znaleziono szybki i brudny sposób, który wydaje się co najmniej nieco pracy na 0.5.8 i 0.6. Nie przetestowano go z dziedziczeniem lub innymi funkcjami, które mogą źle wchodzić w interakcje. Dotyczy to także niektórych niepublicznych interfejsów API, więc uważaj na awarie podczas zmiany wersji.

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 

Przykład użycia:

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 
+1

Niestety, wzorzec użycia ma wiele obliczeń dla wielu małych obiektów, więc lokalne buforowanie nie jest tak pomocne. Pomysł klonowania naprawdę brzmi jak droga, dzięki za szybką wskazówkę. Twój końcowy komentarz jest dokładnie taki, jaki chciałbym: poproś programistę o stworzenie "niezabezpieczonej" klasy, ponieważ wiem, że jest to tylko do odczytu. – CarlS

+0

Wielkie dzięki! Nie mogę się doczekać, aby to wypróbować. – CarlS

+0

Zrobiłem kilka wstępnych prac dotyczących sugerowanego hakowania map, a różnice w czasie są zachęcające. Dla prostej pętli: bom w xrange (500000): foo = readonlyobj.attr_bar z normalnego oprzyrządowania: 2.663 sek z readonly odwzorowujący Hack: 0,078 sek To bardzo znaczący wynik imo, więc dzięki jeszcze raz. Nadal staram się naprawdę zrozumieć, jak to działa i jest to świetny sposób, aby nauczyć się sqlalchemy z nieco większą głębią. – CarlS

-1

Spróbuj użyć pojedynczego zapytania z łączami zamiast pętli Pythona.

+0

Dzięki, ale nie jest punktem ORM jest, że te pojemniki zostaną inteligentnie zaludnionych dla mnie? Nie chciałbym stracić tej korzyści. Zrobiłem również pewne ograniczone testy i może być wolniej uruchomić duże zapytanie i przetwarzać wiersz ResultProxy za wierszem, w którym to momencie nadal płacę za dostęp do "foo.property". – CarlS

+0

Rzeczy ORM to tylko ułatwienie, aby ułatwić pracę z rdbms w sposób zorientowany obiektowo. To nie jest miejsce, aby usunąć relacje z relacyjnych baz danych. – ebo

0

Powinieneś być w stanie wyłączyć leniwy ładunek w związkach, o które chodzi, a sqlalchemy pobierze je wszystkie w jednym zapytaniu.

+0

To nie jest szybkość zapytania, ale prosty sposób na zrobienie wielu tysięcy "oprzyrządowanych" wejść do właściwości obiektu, np. "Foo.property". – CarlS

+0

Ten wzorzec użycia, gdy jest załadowany z opóźnieniem, często generuje oddzielną instrukcję select dla każdej iteracji każdej pętli. (Zwykle widoczne, jeśli włączysz wyjście SQL podczas testów.) Dlatego moja pierwsza odpowiedź była taka. –

+0

OK, sprawdzę to jeszcze raz: kiedy ostatnio debugowałem, pamiętam, że widziałem z góry kilka SQL, ale nigdy podczas samych pętli.Powinienem zaznaczyć, że piszę symulator monte-carlo, więc te pętle są uruchamiane 100000 razy (muszę sprawdzić, czy SQL do pobrania kontenerów jest robiony tylko raz). – CarlS

Powiązane problemy