簡體   English   中英

許多詞典使用大量的RAM

[英]Many dictionaries using massive amounts of RAM

我有一個非常簡單的Python腳本來創建(用於測試目的),列表中有3500萬個字典對象。 每個字典對象包含兩個鍵/值對。 例如。

{'Name': 'Jordan', 'Age': 35}

該腳本非常簡單地對名稱和年齡進行查詢,搜索字典列表並返回包含所有匹配字典條目索引的新列表。

但是,如下所示, 消耗了大量內存 我認為我在某個地方犯了一個非常天真的錯誤。

代碼和任務管理器的屏幕截圖顯示ram使用情況

我的代碼如下:(如果更具可讀性,也可以在圖像中查看)。

import sys

# Firstly, we will create 35 million records in memory, all will be the same apart from one

def search(key, value, data, age):
    print("Searching, please wait")
    # Create list to store returned PKs
    foundPKS = []
    for index in range(0, len(data)):
        if key in data[index] and 'Age' in data[index]:
            if data[index][key] == value and data[index]['Age'] >= age:
                foundPKS.append(index)
    results = foundPKS
    return results

def createdata():
    # Let's create our list for storing our dictionaries
    print("Creating database, please wait")
    dictList = []
    for index in range(0, 35000000):
        # Define dictionary
        record = {'Name': 'Jordan', 'Age': 25}
        if 24500123 <= index <= 24500200:
            record['Name'] = 'Chris'
            record['Age'] = 33
        # Add the dict to a list
        dictList.append(record)
    return dictList

datareturned = createdata()

keyname = input("For which key do you wish to search?")
valuename = input("Which values do you want to find?")
valueage = input("What is the minimum age?")

print("Full data set object size:" + str(sys.getsizeof(datareturned)))
results = search(keyname, valuename, datareturned, int(valueage))

if len(results) > 0:
    print(str(len(results)) + " found. Writing to results.txt")
    fo = open("results.txt", "w")
    for line in range(0, len(results)):
        fo.write(str(results[line]) + "\n")
    fo.close()

什么導致大量消耗RAM?

dict對象的開銷非常大。 這取決於您的Python版本和系統架構,但取決於Python 3.5 64位

In [21]: sys.getsizeof({})
Out[21]: 288

所以估計:

250*36e6*1e-9 == 9.0

所以這是一個下限在千兆字節我的內存使用情況,如果我創造了很多字典,而不是在保理list

而不是使用dict作為記錄類型,而不是用例,請使用namedtuple

為了了解這是如何比較的,讓我們設置一個等效的元組列表:

In [23]: Record = namedtuple("Record", "name age")

In [24]: records = [Record("john", 28) for _ in range(36000000)]

In [25]: getsizeof = sys.getsizeof

考慮:

In [31]: sum(getsizeof(record)+ getsizeof(record.name) + getsizeof(record.age)  for record in records)
Out[31]: 5220000000

In [32]: _ + getsizeof(records)
Out[32]: 5517842208

In [33]: _ * 1e-9
Out[33]: 5.517842208

所以5演出是一個相當保守的上限。 例如,它假設沒有小型int緩存,對於記錄類型的年齡而言 ,這將完全重要。 在我自己的系統上,python進程正在注冊2.7 GB的內存使用量(通過top )。

因此,在我的機器中實際發生的事情更好地建模為保守字符串假設 - 平均大小為10的唯一字符串,因此沒有字符串實習 - 但是對於整數而言是自由的,假設int-caching正在處理我們的int對象,所以我們只需要擔心8字節的指針!

In [35]: sum(getsizeof("0123456789") + 8  for record in records)
Out[35]: 2412000000

In [36]: _ + getsizeof(records)
Out[36]: 2709842208

In [37]: _ * 1e-9
Out[37]: 2.709842208

對於我從top觀察的內容來說,這是一個很好的模型。

如果你真的想要高效存儲

現在,如果你真的想把數據塞入ram,那么你將不得不失去Python的靈活性。 您可以將array模塊與struct結合使用,以獲得類似C的內存效率。 一個更容易涉足的世界可能是numpy ,這允許類似的事情。 例如:

In [1]: import numpy as np

In [2]: recordtype = np.dtype([('name', 'S20'),('age', np.uint8)])

In [3]: records = np.empty((36000000), dtype=recordtype)

In [4]: records.nbytes
Out[4]: 756000000

In [5]: records.nbytes*1e-9
Out[5]: 0.756

請注意,我們現在可以非常緊湊。 我可以使用8位無符號整數(即單個字節)來表示年齡。 但是,我立即面臨一些不靈活性:如果我想要有效存儲字符串,我必須定義最大尺寸。 我用了'S20' ,這是20個字符。 這些是ASCII字節,但是20個ascii字符的字段可能足以滿足名稱。

現在, numpy為您提供了許多包裝C編譯代碼的快速方法。 所以,只是為了解決它,讓我們用一些玩具數據填充我們的記錄。 名稱將只是簡單計數的數字串,年齡將從正態分布中選擇,平均值為50,標准差為10。

In [8]: for i in range(1, 36000000+1):
   ...:     records['name'][i - 1] = b"%08d" % i
   ...:

In [9]: import random
   ...: for i in range(36000000):
   ...:     records['age'][i] = max(0, int(random.normalvariate(50, 10)))
   ...:

現在,我們可以使用numpy來查詢我們的records 例如,如果您希望記錄的索引具有某些條件 ,請使用np.where

In [10]: np.where(records['age'] > 70)
Out[10]: (array([      58,      146,      192, ..., 35999635, 35999768, 35999927]),)

In [11]: idx = np.where(records['age'] > 70)[0]

In [12]: len(idx)
Out[12]: 643403

所以643403年齡> 70記錄。 現在,讓我們試試100

In [13]: idx = np.where(records['age'] > 100)[0]

In [14]: len(idx)
Out[14]: 9

In [15]: idx
Out[15]:
array([ 2315458,  5088296,  5161049,  7079762, 15574072, 17995993,
       25665975, 26724665, 28322943])

In [16]: records[idx]
Out[16]:
array([(b'02315459', 101), (b'05088297', 102), (b'05161050', 101),
       (b'07079763', 104), (b'15574073', 101), (b'17995994', 102),
       (b'25665976', 101), (b'26724666', 102), (b'28322944', 101)],
      dtype=[('name', 'S20'), ('age', 'u1')])

當然,一個主要的缺點是numpy數組的大小 調整大小的操作很昂貴。 現在,你可以在一些類中包裝一個numpy.array ,它將作為一個有效的主干,但在那時,你也可以使用一個真正的數據庫。 幸運的是,Python附帶了sqlite

我們來看看這個

>>> import sys 
>>> sys.getsizeof({'Name': 'Jordan', 'Age': 25}) * 35000000
10080000000

所以~10 GB。 Python正在做你要求它做的事情。

您需要將其拆分為夾頭並按順序檢查它們。 以此為出發點

...列表中有3500萬個字典對象。 每個字典對象包含兩個鍵/值對。 例如。 {'姓名':'喬丹','年齡':35}

你是對的,這種存儲方式有相當大的開銷。

Flyweight設計模式表明該解決方案涉及分解共性。 以下是兩種相同數據的替代存儲方案,具有更好的空間利用率。

您可以使用__slots__來節省類的實例空間(這會禁止創建每個實例的字典):

class Person(object):
    __slots__ = ['Name', 'Age']

s = [Person('Jordan', 35), Person('Martin', 31), Person('Mary', 33)]

使用像一對並行列表這樣的密集數據結構更加節省空間:

s_name = ['Jordan', 'Martin', 'Mary']
s_age = [35, 31, 33]

如果在數據復制,您節省更多的空間實習值:

s_name = map(intern, s_name)

或者在Python 3中:

s_name = list(map(sys.intern, s_name)

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM