2010-06-11 100 views
9

我需要優化我的應用程序的RAM使用情況。
請給我講課,告訴我編碼Python時不應該關心內存。我有一個內存問題,因爲我使用非常大的默認字典(是的,我也想快)。我目前的內存消耗是350MB並且在增長。我已經無法使用共享主機,並且如果我的Apache打開更多進程,則內存會增加一倍和三倍......並且它很昂貴。
我已經完成了廣泛的分析,我確切地知道我的問題在哪裏。
我有幾個具有Unicode密鑰的大型(> 100K條目)字典。字典從140字節開始並快速增長,但更大的問題是密鑰。 Python對內存中的字符串進行了優化(或者我已經閱讀過),以便查找可以進行ID比較('interning')。不知道這對於unicode字符串也是如此(我無法'實習'他們)。
存儲在字典中的對象是元組列表(an_object,int,int)。內存優化的Python技巧

my_big_dict [some_unicode_string] .append((my_object,an_int,another_int))

我已經發現,它是值得的分成幾個字典,因爲元組採取了很多的空間.. 。
我發現我可以在使用它們作爲鍵之前通過散列字符串來保存RAM!然後,不幸的是,我碰到了我的32位系統上的生日碰撞。 (側面的問題:我可以在32位系統上使用64位密鑰字典嗎?)

在Linux(生產)和Windows上均爲Python 2.6.5。 關於優化字典/列表/元組的內存使用的任何提示? 我甚至想過使用C - 我不在乎這個非常小的代碼是否很難看。這只是一個獨特的位置。

提前致謝!

+0

2個小評論:我真的很喜歡開箱即用的系統級答案,但是它們(例如數據庫,即使是緩存)真的可以在性能上與Python字典進行比較嗎?我正在運行實時算法,字典速度不夠快。我一定會嘗試memcached和Redis(很酷),但是進程間通信對我來說足夠快嗎? (對不起,現在就添加這個,很難優化內存和速度......) 另外,我的字典大部分是隻讀的。我能否以某種方式使用這些知識? – 2010-06-11 10:59:10

+1

「PEP 412:密鑰分享字典」可能會引起您的興趣。我相信它包含在Python 3.3中http://www.python.org/dev/peps/pep-0412/ – bcoughlan 2013-08-13 23:13:09

+0

@bcoughlan非常酷,謝謝!可悲的是我需要等待2.7回程。 – 2013-08-15 07:46:31

回答

10

我建議如下:將所有值存儲在一個數據庫中,並將字符串散列作爲關鍵字保存在內存中的字典中。如果發生衝突,則從數據庫獲取值,否則(絕大多數情況下)會使用字典。實際上,它將是一個巨大的緩存。

在Python字典的一個問題是它們使用了大量的空間:甚至一個int-INT字典使用每鍵值對45-80字節 32位系統。同時,一個array.array('i')只使用8個字節每一對整數,並與一點簿記一個可以實現一個相當快的基於陣列的int→int字典。

一旦你有一個內存高效實現的int-INT詞典,分裂您串→(對象,INT,INT)詞典分成三個詞典和使用哈希值,而不是滿弦。您將獲得一個int→對象和兩個int→int字典。仿真int→對象字典,如下所示:將對象的列表和對象的索引保存爲int→int字典的值。

我意識到有相當多的編碼涉及到基於數組的字典。我遇到過類似於你的問題,我已經實現了一個相當快速,非常有效的內存通用hash-int字典。 Here's my code(BSD許可證)。它基於數組(每對8字節),它處理關鍵字散列和衝突檢查,它在寫入期間保持數組(實際上是幾個較小的數組),並在讀取時進行二進制搜索。您的代碼減少到這樣的:

dictionary = HashIntDict(checking = HashIntDict.CHK_SHOUTING) 
# ... 
database.store(k, v) 
try: 
    dictionary[k] = v 
except CollisionError: 
    pass 
# ... 
try: 
    v = dictionary[k] 
except CollisionError: 
    v = database.fetch(k) 

checking參數指定當發生碰撞時會發生什麼:CHK_SHOUTING上讀取提高CollisionError和寫入,CHK_DELETING回報None上讀取並保持沉默寫入,CHK_IGNORING不做碰撞檢查。

以下是對我的實現的簡要說明,歡迎提供優化提示!頂級數據結構是一個常規的數組字典。每個數組最多包含2^16 = 65536個整數對(2^32的平方根)。鍵k和相應的值v都存儲在k/65536排列中。這些數組按需進行初始化並按鍵排序。二進制搜索在每次讀取和寫入時執行。碰撞檢查是一種選擇。如果啓用,嘗試覆蓋已存在的密鑰將從字典中刪除密鑰和相關值,將密鑰添加到一組衝突密鑰中,並(可選地)引發異常。

1

使用shelve或數據庫來存儲數據而不是內存字典。

2

我曾經遇到過一些大對象的集合,我需要根據幾個元數據屬性對不同的方法進行排序和過濾。我不需要它們中較大的部分,所以我將它們傾倒在磁盤上。

由於數據的類型非常簡單,一個快速的SQLite數據庫可能會解決所有的問題,甚至會加速一點點。

4

對於Web應用程序,您應該使用數據庫,您要這樣做的方式是爲每個Apache進程創建一個dict副本,這非常浪費。如果服務器上有足夠的內存,數據庫表將被緩存在內存中(如果您沒有足夠的空間來存放表的一個副本,請將更多內存放入服務器)。只要記住在你的數據庫表上放置正確的索引,否則你的性能會很差。

0

如果您想進行廣泛的優化並完全控制內存使用情況,您也可以編寫一個C/C++模塊。使用Swig封裝到Python中的代碼可以很容易地完成,與純C Python模塊相比,性能開銷較小。

1

如果你想留在內存中的數據存儲,你可以嘗試像memcached

這樣,您可以使用來自所有Python進程的單個內存中鍵/值存儲。

有幾個python memcached客戶端庫。

+3

memcached是有損的,因此不適合數據存儲。 – 2010-06-11 08:51:37

1

Redis如果您可以選擇在共享主機上使用它 - 這與memcached類似,但針對數據結構進行了優化,這將是一個不錯的選擇。 Redis也支持python綁定。

我使用它在日常的基礎上進行數字運算,但也可以在生產系統中用作數據存儲,並且不能推薦它足夠高。

另外,你有選擇代理你的應用程序背後的nginx而不是使用Apache?你可能會發現(如果允許的話)這個代理/ Web應用程序安排對資源的渴望較少。

祝你好運。