2010-02-23 3 views
8

Ho una complessa rete di oggetti generata da un database SQLite che utilizza i mapping ORM sqlalchemy. Ho un bel po 'di nidificazione:Ottimizzazioni SqlAlchemy per i modelli di oggetti di sola lettura

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

mio profilazione mi sta dimostrando che la strumentazione sqlalchemy sta prendendo un sacco di tempo in questo caso d'uso.

Il fatto è: non cambio mai il modello di oggetto (proprietà mappate) in fase di esecuzione, quindi una volta caricati non ho bisogno della strumentazione, o del tutto di un sovraccarico di sqlalchemy. Dopo molte ricerche, penso che potrei dover clonare un insieme di oggetti "puro pitone" dai miei "oggetti strumentati" già caricati, ma sarebbe un dolore.

Le prestazioni sono davvero cruciali qui (è un simulatore), quindi forse scrivere quei livelli come estensioni C usando direttamente sqlite api sarebbe il migliore. qualche idea?

risposta

7

Se si fa riferimento a un singolo attributo di una singola istanza molte volte, un semplice trucco è memorizzarlo in una variabile locale.

Se si desidera un modo per creare a basso costo cloni puro Python, condividere l'oggetto dict con l'oggetto originale:

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

Creazione di una copia del genere costa circa la metà della accesso agli attributi strumentato e attribuiamo le ricerche sono i veloce come al solito.

Potrebbe anche esserci un modo per fare in modo che il mapper crei istanze di una classe non strumentale al posto di quella strumentata. Se ho un po 'di tempo, potrei dare un'occhiata a quanto profondamente radicato sia l'assunto che le istanze popolate siano dello stesso tipo della classe strumentata.


Trovato un modo rapido e sporco che sembra funzionare almeno in qualche modo su 0.5.8 e 0.6. Non l'ho provato con l'ereditarietà o altre caratteristiche che potrebbero interagire male. Inoltre, questo tocca alcune API non pubbliche, quindi fai attenzione alle interruzioni quando cambi le versioni.

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 

Esempio di utilizzo:

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

Sfortunatamente il modello di utilizzo è costituito da molti calcoli su molti piccoli oggetti, quindi la cache locale non è così utile. L'idea della clonazione suona davvero come la strada da percorrere, grazie per il suggerimento rapido. Il tuo commento finale è esattamente quello che vorrei: chiedi al mapper di creare una classe 'non documentata', perché so che è di sola lettura. – CarlS

+0

Grazie mille! Non vedo l'ora di provarlo. – CarlS

+0

Ho già fatto alcuni lavori iniziali sull'hack dei mapper e le differenze di orario sono incoraggianti. Per un semplice ciclo: for i in xrange (500000): foo = readonlyobj.attr_bar con strumentazione normale: 2.663 sec con sola lettura mod mapper: 0.078 sec Questo è un risultato molto significativo imo, quindi grazie ancora. Sto ancora cercando di capire veramente come funziona e si sta rivelando un ottimo modo per imparare sqlalchemy in un po 'più in profondità. – CarlS

-1

Provare a utilizzare una singola query con JOINs invece dei loop python.

+0

Grazie, ma non è il punto della ORM essere che quei contenitori sarà intelligente popolato per me? Non vorrei perdere quel beneficio. Ho anche fatto alcuni test limitati e in realtà può essere più lento eseguire una query di grandi dimensioni ed elaborare ResultProxy riga per riga, a quel punto sto ancora pagando per l'accesso 'foo.property'. – CarlS

+0

Gli oggetti ORM sono solo una comodità per semplificare il lavoro con rdbms in modo orientato agli oggetti. Non è lì per prendere la relazione fuori dal db relazionale. – ebo

0

Dovresti essere in grado di disabilitare il caricamento lazy sulle relazioni in questione e sqlalchemy le recupererà tutte in una singola query.

+0

Non è tanto la velocità della query quanto il semplice sovraccarico di fare migliaia di accessi "strumentati" alle proprietà dell'oggetto, ad esempio "foo.property". – CarlS

+0

Questo modello di utilizzo, quando è pigro caricato, genera spesso un'istruzione select separata per ogni iterazione di ogni ciclo. (Solitamente visibile se si attiva l'output SQL durante le esecuzioni di test.) Ecco perché la mia prima risposta è stata questa. –

+0

ok, controllerò due volte: l'ultima volta che ho eseguito il debug mi ricordo di aver visto un gruppo di SQL in anticipo ma nessuno durante i loop stessi.Dovrei sottolineare che sto scrivendo un simulatore di monte-carlo, quindi questi cicli vengono eseguiti 100000 volte (devo controllare che l'SQL per recuperare i contenitori venga eseguito una sola volta). – CarlS