2010-02-23 47 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