2010-02-23 10 views
8

Tengo una compleja red de objetos generados a partir de una base de datos sqlite usando asignaciones ORM sqlalchemy. Tengo un buen número anidada:Optimizaciones SqlAlchemy para modelos de objetos de solo lectura

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

Mi perfilado me está mostrando que la instrumentación sqlalchemy está tomando mucho tiempo en este caso de uso.

La cosa es: nunca cambio el modelo de objeto (propiedades mapeadas) en tiempo de ejecución, así que una vez que se cargan NO NECESITO la instrumentación, o de hecho cualquier sobrecarga de sqlalchemy en absoluto. Después de mucha investigación, estoy pensando que podría tener que clonar un conjunto de objetos de "pitón puro" de mis ya cargados "objetos instrumentados", pero eso sería un dolor.

El rendimiento es realmente crucial aquí (es un simulador), así que tal vez sea mejor escribir esas capas como extensiones C usando sqlite api directamente. ¿Alguna idea?

Respuesta

7

Si hace referencia a un solo atributo de una sola instancia muchas veces, un simple truco es almacenarlo en una variable local.

Si quieres una manera de crear clones pitón puros baratos, comparte el objeto dict con el objeto original:

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

Creación de una copia como esto cuesta aproximadamente la mitad del acceso atributo instrumentado y atribuyen las búsquedas son tan rápido como es normal

También puede haber una manera de que el asignador cree instancias de una clase sin instrumentación en lugar de la instrumentada. Si tengo algo de tiempo, podría echar un vistazo a cuán arraigada está la suposición de que las instancias pobladas son del mismo tipo que la clase instrumentada.


Encontré una manera rápida y sucia que al menos parece funcionar en 0.5.8 y 0.6. No lo probé con herencia u otras características que podrían interactuar mal. Además, esto toca algunas API no públicas, así que ten cuidado con las roturas cuando cambies de versión.

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 

Ejemplo de uso:

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

Desafortunadamente, el patrón de uso es muchos cálculos en muchos objetos pequeños, por lo que el almacenamiento en caché local no es tan útil. La idea de clonación realmente suena como el camino a seguir, gracias por el consejo rápido. Su comentario final es exactamente lo que me gustaría: pedirle al mapeador que cree una clase 'sin instrumentos', porque sé que es de solo lectura. – CarlS

+0

¡Muchas gracias! No puedo esperar para probar esto. – CarlS

+0

He hecho un trabajo inicial sobre el mapeo de mapeo sugerido y las diferencias de tiempo son alentadoras. Para un bucle simple: para i en xrange (500000): foo = readonlyobj.attr_bar con instrumentos normales: 2.663 segundos con truco asignador de sólo lectura: 0.078 segs Eso es un resultado muy significativo de la OMI, así que gracias de nuevo. Todavía estoy tratando de entender realmente cómo funciona y está demostrando ser una excelente manera de aprender sqlalchemy con más profundidad. – CarlS

-1

Intente utilizar una única consulta con JOIN en lugar de los bucles de python.

+0

Gracias, pero no es el punto de la ORM ser que esos contenedores se rellenará de forma inteligente para mí? Odiaría perder ese beneficio. También hice algunas pruebas limitadas y puede ser más lento ejecutar una consulta grande y procesar el ResultProxy fila por fila, en ese punto todavía estoy pagando por el acceso 'foo.property'. – CarlS

+0

El material de ORM es solo una conveniencia para facilitar el trabajo con rdbms de una manera orientada a objetos. No está ahí para sacar lo relacional de los dbs relacionales. – ebo

0

Debería poder deshabilitar la carga diferida en las relaciones en cuestión y sqlalchemy las obtendrá todas en una sola consulta.

+0

No es tanto la velocidad de la consulta como la simple sobrecarga de hacer muchos miles de accesos 'instrumentados' a las propiedades del objeto, es decir, 'foo.property'. – CarlS

+0

Este patrón de uso, cuando se carga de forma diferida, a menudo generará una instrucción de selección separada para cada iteración de cada ciclo. (Por lo general, es visible si activa la salida SQL durante las pruebas). Es por eso que mi primera respuesta fue esta. –

+0

bien, voy a comprobar esto: la última vez que me depuré, recuerdo haber visto un montón de SQL por adelantado, pero ninguno durante los bucles.Debo señalar que estoy escribiendo un simulador monte-carlo, por lo que estos bucles se ejecutan 100000 veces (tengo que comprobar que el SQL para buscar los contenedores solo se hace una vez). – CarlS

Cuestiones relacionadas