2010-02-23 50 views
8

我有一个使用sqlalchemy ORM映射从sqlite数据库生成的复杂对象网络。我有好几个深嵌套:只读对象模型的SqlAlchemy优化

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

我的分析是向我展示了SQLAlchemy的仪器正在采取了大量的时间在这个用例。

问题是:我不会在运行时更改对象模型(映射属性),因此一旦它们被加载,我不需要工具,或者实际上任何sqlalchemy开销。经过大量研究,我想我可能不得不从我已经加载的'instrumented对象'中克隆一个'纯Python'对象集合,但那会很痛苦。 (这是一个模拟器),所以也许使用sqlite API直接编写这些图层作为C扩展是最好的。有什么想法吗?

回答

7

如果您多次引用单个实例的单个属性,一个简单的技巧就是将其存储在本地变量中。

如果你想有一个方法来创建便宜的纯Python的克隆,分享与原对象的字典对象:

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

不幸的是,使用模式是许多小对象的许多计算,所以本地缓存并不是那么有用。克隆想法听起来像是要走的路,感谢快速提示。您的最终评论正是我想要的:请映射器创建一个“未经修复”的类,因为我知道它是只读的。 – CarlS 2010-02-24 03:42:26

+0

非常感谢!我迫不及待想尝试一下。 – CarlS 2010-02-24 05:49:12

+0

我已经完成了关于mapper hack的一些初步工作,并且时间差异令人鼓舞。对于一个简单的循环: 因我的xrange(500000):富= readonlyobj.attr_bar 正常仪器:2.663秒 与只读映射黑客:0.078秒 国际海事组织(IMO)是一个非常显著的结果,所以再次感谢。我仍然试图真正理解它是如何工作的,这证明了一个更深入学习sqlalchemy的好方法。 – CarlS 2010-03-02 04:18:12

-1

尝试使用JOIN代替python循环的单个查询。

+0

感谢,但不是ORM的点是,这些容器将智能填充给我吗?我不想失去这种好处。我也做了一些有限的测试,实际上运行一个大的查询并逐行处理ResultProxy的速度会更慢,此时我仍然支付'foo.property'访问权限。 – CarlS 2010-02-24 03:48:13

+0

ORM的东西只是一个方便,以便于以面向对象的方式使用rdbms。它不适合从关系数据库中取出关系数据库。 – ebo 2010-02-24 20:53:50

0

您应该可以禁用有关关系的延迟加载,而sqlalchemy将在单个查询中将它们全部提取出来。

+0

这不是查询的速度,而是做了许多'仪器化'访问对象属性的简单开销,即'foo.property'。 – CarlS 2010-02-24 03:49:14

+0

这种使用模式在延迟加载时,通常会为每个循环的每次迭代生成一个单独的select语句。 (通常在测试运行期间打开SQL输出时可见。)这就是为什么我的第一个回应是这样的。 – 2010-02-24 06:19:42

+0

好吧,我会仔细检查一下:上次我调试过,我记得在循环中看到了一堆SQL,但没有。我应该指出,我正在编写一个monte-carlo模拟器,因此这些循环正在运行100000次(我需要检查SQL容器是否只能执行一次)。 – CarlS 2010-02-24 07:12:53