Python 许多字典使用大量内存
我有一个非常简单的Python脚本(出于测试目的)在一个列表中创建3500万个字典对象。每个dictionary对象包含两个键/值对。例如Python 许多字典使用大量内存,python,python-3.x,dictionary,memory,optimization,Python,Python 3.x,Dictionary,Memory,Optimization,我有一个非常简单的Python脚本(出于测试目的)在一个列表中创建3500万个字典对象。每个dictionary对象包含两个键/值对。例如 {'Name': 'Jordan', 'Age': 35} 该脚本只需查询姓名和年龄,搜索字典列表并返回一个新列表,其中包含所有匹配字典条目的索引 然而,正如你在下面看到的,大量的内存被消耗掉了。我想我在某个地方犯了一个非常幼稚的错误 我的代码如下:(如果可读性更好,也可以在图像中查看) 导入系统 #首先,我们将在内存中创建3500万条记录,除了一条记录
{'Name': 'Jordan', 'Age': 35}
该脚本只需查询姓名和年龄,搜索字典列表并返回一个新列表,其中包含所有匹配字典条目的索引
然而,正如你在下面看到的,大量的内存被消耗掉了。我想我在某个地方犯了一个非常幼稚的错误
我的代码如下:(如果可读性更好,也可以在图像中查看)
导入系统
#首先,我们将在内存中创建3500万条记录,除了一条记录外,所有记录都是一样的
def搜索(键、值、数据、年龄):
打印(“正在搜索,请稍候”)
#创建列表以存储返回的PK
foundPKS=[]
对于范围(0,len(数据))中的索引:
如果输入数据[索引]并输入数据[索引]中的“年龄”:
如果数据[index][key]==值,数据[index][Age']>=年龄:
foundPKS.append(索引)
结果=foundPKS
返回结果
def createdata():
#让我们创建存储词典的列表
打印(“正在创建数据库,请稍候”)
dictList=[]
对于范围内的索引(0,35000000):
#定义字典
记录={'Name':'Jordan','Age':25}
如果24500123让我们看看这个
>>> import sys
>>> sys.getsizeof({'Name': 'Jordan', 'Age': 25}) * 35000000
10080000000
所以大约10 GB。Python正在做您要求它做的事情
您需要将其拆分为多个卡盘,并按顺序进行检查。作为起点,尝试dict对象的开销相当大。这取决于您的Python版本和系统架构,但取决于Python 3.5 64位
In [21]: sys.getsizeof({})
Out[21]: 288
所以猜测:
250*36e6*1e-9 == 9.0
因此,如果我创建了那么多字典,而不是在列表
中进行分解,那么这就是我在gigabytes中ram使用的下限
与其使用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.7G的内存使用量(通过top
)
因此,我的机器中实际发生的事情最好是对字符串进行保守建模,假设唯一字符串的平均大小为10,因此没有字符串插入,但对int来说是自由的,假设int缓存为我们处理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位无符号整数(即单字节)来表示年龄。然而,我马上就面临一些不灵活的问题:如果我想要有效地存储字符串,我必须定义一个最大大小。我使用了20个字符的'S20'
。这些是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查询记录。例如,如果希望记录的索引满足某些条件,请使用np。其中:
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
。。。列表中有3500万个字典对象。每个字典对象
包含两个键/值对。例如,{'Name':'Jordan','Age':35}
您是对的,这种存储方式有相当大的开销
研究表明,解决方案需要考虑到共同点。这里有两种方法可以更好地利用空间来替代相同数据的存储
您可以使用来节省类实例上的空间(这将禁止创建每个实例的字典):
使用密集的数据结构(如一对平行列表)更节省空间:
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)
是的,不要用3600万条。使用元组或更好的名称元组我假设RAM的使用是由dict所需的开销造成的,因为原始字符串数据只会使250-300MB非常有用。谢谢我确实希望数据保留在内存中以提高性能。一个Dict似乎要消耗相当多的开销。@Jordan-pandas
可能也是一个不错的选择。它还将使搜索和匹配变得更加容易,因为pandas
已经构建了该功能。。。或者直接购买moar RAM:)看起来如果我真的想在内存中维护数据,一个命名的双工将成为赢家。非常有用,谢谢。@Jordan我要添加一个comparison@Jordan这是一个有价值的改进
s_name = map(intern, s_name)
s_name = list(map(sys.intern, s_name)