在python中保存和处理内存中大字典的有效方法

时间:2013-08-04 10:13:44

标签: python dictionary

当我做了一点测试时,一个3万个项目的int => int(不同值)的python dict可以很容易地在我的mac上吃掉> 2G内存。由于我只使用int到int dict,有没有比使用python dict更好的解决方案?

我需要的一些要求是,

  1. 将数百万级int保留为int项目的内存效率更高
  2. 基本的dict方法,例如按键获取值并迭代所有项
  3. 易于序列化为字符串/二进制将是一个加号
  4. 更新,  4.通过给定的键很容易获得子集,例如d.fromkeys([...])

    感谢。

4 个答案:

答案 0 :(得分:6)

至少有两种可能性:

<强>阵列

您可以尝试使用两个阵列。一个用于键,一个用于值,以便索引(键)==索引(值)

2017-01-05更新:在数组中使用4字节整数。

数组会占用更少的内存。在使用clang编译的python的64位FreeBSD机器上,一个3000万个整数的数组使用大约117 MiB。

这些是我使用的python命令:

Python 2.7.13 (default, Dec 28 2016, 20:51:25) 
[GCC 4.2.1 Compatible FreeBSD Clang 3.8.0 (tags/RELEASE_380/final 262564)] on freebsd11
Type "help", "copyright", "credits" or "license" for more information.
>>> from array import array
>>> a = array('i', xrange(30000000))
>>> a.itemsize
4

导入数组后,ps报告:

USER     PID %CPU %MEM   VSZ  RSS TT  STAT STARTED    TIME COMMAND
 rsmith 81023  0.0  0.2  35480   8100  0  I+   20:35     0:00.03 python (python2.7)

制作阵列后:

USER     PID %CPU %MEM    VSZ    RSS TT  STAT STARTED    TIME COMMAND
rsmith 81023 29.0  3.1 168600 128776  0  S+   20:35     0:04.52 python (python2.7)

居民集大小以1 KiB为单位报告,因此(128776 - 8100)/ 1024 = 117 MiB

使用列表推导,您可以轻松获得密钥满足特定条件的索引列表。然后,您可以使用该列表中的索引来访问相应的值...

<强> numpy的

如果你有可用的numpy,使用它更快,有更多的功能,并使用稍微少的RAM:

Python 2.7.5 (default, Jun 10 2013, 19:54:11) 
[GCC 4.2.1 Compatible FreeBSD Clang 3.1 ((branches/release_31 156863))] on freebsd9
Type "help", "copyright", "credits" or "license" for more information.
>>> import numpy as np
>>> a = np.arange(0, 30000000, dtype=np.int32)

ps:启动Python后的6700 KiB,导入numpy后的17400 KiB和创建阵列后的134824 KiB。这大概是114 MiB。

此外,numpy支持record arrays;

Python 2.7.5 (default, Jun 10 2013, 19:54:11) 
[GCC 4.2.1 Compatible FreeBSD Clang 3.1 ((branches/release_31 156863))] on freebsd9
Type "help", "copyright", "credits" or "license" for more information.
>>> import numpy as np
>>> a = np.zeros((10,), dtype=('i4,i4'))
>>> a
array([(0, 0), (0, 0), (0, 0), (0, 0), (0, 0), (0, 0), (0, 0), (0, 0),
       (0, 0), (0, 0)], 
      dtype=[('f0', '<i4'), ('f1', '<i4')])
>>> a.dtype.names
('f0', 'f1')
>>> a.dtype.names = ('key', 'value')
>>> a
array([(0, 0), (0, 0), (0, 0), (0, 0), (0, 0), (0, 0), (0, 0), (0, 0),
       (0, 0), (0, 0)], 
      dtype=[('key', '<i4'), ('value', '<i4')])
>>> a[3] = (12, 5429)
>>> a
array([(0, 0), (0, 0), (0, 0), (12, 5429), (0, 0), (0, 0), (0, 0), (0, 0),
       (0, 0), (0, 0)], 
      dtype=[('key', '<i4'), ('value', '<i4')])
>>> a[3]['key']
12

您可以在此处分别访问键和值;

>>> a['key']
array([ 0,  0,  0, 12,  0,  0,  0,  0,  0,  0], dtype=int32)

答案 1 :(得分:2)

基于Judy阵列的解决方案似乎是我应该考虑的选项。我仍然在寻找可以被Python使用的好实现。稍后会更新。

更新,

最后我正在http://code.google.com/p/py-judy/试验一个Judy数组包装器。 似乎没有任何文件,但我试图简单地通过dir(...)它的包和对象找到它的方法,但它的工作原理。

同样的实验,它使用judy.JudyIntObjectMap在标准字典的1/3处吃〜986MB。它还提供了JudyIntSet,它在某些特殊场景中将节省更多内存,因为与JudyIntObjectMap相比,它不需要引用任何真正的Python对象作为值。

(如下面进一步测试,JudyArray只使用几MB到几十MB,大多数~986MB实际上是由Python内存空间中的值对象使用。)

以下是一些代码,如果它对您有帮助,

>>> import judy
>>> dir(judy)
['JudyIntObjectMap', 'JudyIntSet', '__doc__', '__file__', '__name__', '__package__']
>>> a=judy.JudyIntObjectMap()
>>> dir(a)
['__class__', '__contains__', '__delattr__', '__delitem__', '__doc__', '__format__', '__getattribute__', '__getitem__', '__hash__', '__init__', '__iter__', '__len__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__setitem__', '__sizeof__', '__str__', '__subclasshook__', '__value_sizeof__', 'by_index', 'clear', 'get', 'iteritems', 'iterkeys', 'itervalues', 'pop']
>>> a[100]=1
>>> a[100]="str"
>>> a["str"]="str"
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
KeyError: 'non-integer keys not supported'
>>> for i in xrange(30000000):
...     a[i]=i+30000000   #finally eats ~986MB memory
... 

更新

好的,测试的30M int的JudyIntSet。

>>> a=judy.JudyIntSet()
>>> a.add(1111111111111111111111111)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ValueError: we only support integers in the range [0, 2**64-1]

它完全只使用5.7MB来存储30M连续的int数组[0,30000000],这可能是由于JudyArray的自动压缩。高于709MB是bcz我使用范围(...)而不是更合适的xrange(...)来生成数据。

因此,具有30M int的核心JudyArray的大小是可以忽略的。

如果有人知道更完整的Judy Array包装器实现,请告诉我,因为这个包装器只包装了JudyIntObjectMap和JudyIntSet。对于int-int dict,JudyIntObjectMap仍然需要真正的python对象。如果我们只执行counter_add并设置值,那么将值存储在C空间而不是使用python对象是个好主意。希望有兴趣创建或介绍一个:)

答案 2 :(得分:1)

如果我们对如何使用它有更多了解,可能更容易提出好的解决方案。 您说您希望按键获取值并迭代所有值,但不管您是否需要插入/删除数据。

一种非常有效的数据存储方式是使用array模块。如果您不需要插入/删除数据,则可以只使用两个数组。 “key”数组将被排序,您可以对右键进行二进制搜索。然后你只需从另一个数组中的相同位置选择值。

您可以轻松地将其封装在类似dict的类中。我不知道在某个地方是否有现成的解决方案,但实施起来并不是非常困难。这应该可以帮助你避免使用大量消耗内存的python对象。

但是你可能有其他要求使这种解决方案不切实际/不可能。

答案 3 :(得分:1)

如果您想要的只是一个易于使用的类似字典的计数器,则会添加另一个答案。

High performance Counter object from Python standard library