2011-02-18 50 views
8

我正在爲文檔集合上的搜索引擎寫一個倒排索引。現在,我將索引存儲爲字典的字典。也就是說,每個關鍵字映射到docIDs->發生位置的字典。使用cPickle序列化一個大字典導致MemoryError

的數據模型看起來類似: {字:{DOC_NAME:[location_list]}}

在內存中創建索引工作正常,但是當我嘗試序列化到磁盤,我打的MemoryError。這是我的代碼:

# Write the index out to disk 
serializedIndex = open(sys.argv[3], 'wb') 
cPickle.dump(index, serializedIndex, cPickle.HIGHEST_PROTOCOL) 

就在序列化之前,我的程序使用了大約50%的內存(1.6 Gb)。只要我打電話給cPickle,我的內存使用率就會在崩潰之前高達80%。

爲什麼cPickle使用這麼多的內存序列化?有沒有更好的方法來解決這個問題?

回答

10

cPickle需要使用一堆額外的內存,因爲它確實循環檢測。如果您確定自己的數據沒有周期,您可以嘗試使用編組模塊

+1

工作就像一個魅力。令人難以置信的簡單修復 - 基本上只是將「pickle」改爲「marshal」並完成了。我沒有意識到cPickle執行週期檢測。通過使用元帥,寫入磁盤需要幾秒鐘的時間,而不是20分鐘,內存消耗從30%降低到幾乎0%。謝謝! –

+0

簡單的解決方案加上簡潔的解釋,100%真棒。 – mitchus

+0

很高興知道,謝謝@gnibbler! –

0

還有另一個泡菜庫可以嘗試。也可能有一些cPickle設置可以改變。

其他選項:將你的字典分成小塊和cPickle每一塊。然後把它們放回到一起,當你加載所有東西。

對不起,這是模糊的,我只是寫下我的頭頂。我認爲它可能仍然有幫助,因爲沒有人回答。

0

您可能正在爲此作業使用錯誤的工具。如果你想要保存大量的索引數據,我強烈建議使用帶有ORM(如SQLObjectSQL Alchemy)的SQLite磁盤數據庫(或者當然,只是一個普通的數據庫)。

這些會照顧像兼容性平凡的事情,優化目的格式,而不是在內存同時按住所有的數據,讓你耗盡內存...

補充:因爲我是無論如何,我的工作基本上是完全相同的,但主要是因爲我是一個很好的人,這裏有一個演示程序,它可以滿足您的需求(它會在您當前的目錄中創建一個SQLite文件,如果有文件名稱已經存在,所以把它放在空的第一個地方):

import sqlobject 
from sqlobject import SQLObject, UnicodeCol, ForeignKey, IntCol, SQLMultipleJoin 
import os 

DB_NAME = "mydb" 
ENCODING = "utf8" 

class Document(SQLObject): 
    dbName = UnicodeCol(dbEncoding=ENCODING) 

class Location(SQLObject): 
    """ Location of each individual occurrence of a word within a document. 
    """ 
    dbWord = UnicodeCol(dbEncoding=ENCODING) 
    dbDocument = ForeignKey('Document') 
    dbLocation = IntCol() 

TEST_DATA = { 
    'one' : { 
     'doc1' : [1,2,10], 
     'doc3' : [6], 
    }, 

    'two' : { 
     'doc1' : [2, 13], 
     'doc2' : [5,6,7], 
    }, 

    'three' : { 
     'doc3' : [1], 
    }, 
}   

if __name__ == "__main__": 
    db_filename = os.path.abspath(DB_NAME) 
    if os.path.exists(db_filename): 
     os.unlink(db_filename) 
    connection = sqlobject.connectionForURI("sqlite:%s" % (db_filename)) 
    sqlobject.sqlhub.processConnection = connection 

    # Create the tables 
    Document.createTable() 
    Location.createTable() 

    # Import the dict data: 
    for word, locs in TEST_DATA.items(): 
     for doc, indices in locs.items(): 
      sql_doc = Document(dbName=doc) 
      for index in indices: 
       Location(dbWord=word, dbDocument=sql_doc, dbLocation=index) 

    # Let's check out the data... where can we find 'two'? 
    locs_for_two = Location.selectBy(dbWord = 'two') 

    # Or... 
    # locs_for_two = Location.select(Location.q.dbWord == 'two') 

    print "Word 'two' found at..." 
    for loc in locs_for_two: 
     print "Found: %s, p%s" % (loc.dbDocument.dbName, loc.dbLocation) 

    # What documents have 'one' in them? 
    docs_with_one = Location.selectBy(dbWord = 'one').throughTo.dbDocument 

    print 
    print "Word 'one' found in documents..." 
    for doc in docs_with_one: 
     print "Found: %s" % doc.dbName 

這是證書唯一不是唯一的方式(或者必然是最好的方式)做到這一點。文檔或Word表格是否應與地點表格分開,取決於您的數據和典型用途。在你的情況下,「Word」表可能可能是一個單獨的表,其中有一些額外的索引和唯一性設置。

+0

感謝您的建議。現在,我將使用元帥而不是pickle,但我可能會重新訪問這個元素,並在未來遷移到基於數據庫的解決方案。乾杯! –

+0

@Stephen Poletto - 這很酷,如果marhsal的作品,它的作品,這可以留在這裏爲後代:) – detly