减少大字典使用的内存

时间:2013-09-04 13:19:14

标签: python python-2.7

我需要创建一个内存对象,其中包含9位整数的键和与每个键相关联的布尔值。我一直在使用dict,如下面的简化示例所示:

#!/usr/bin/python
from __future__ import print_function
import sys

myDict = {}
for n in range(56000):
        myDict[n] = True

print('Count:',len(myDict),' Size:', sys.getsizeof(myDict))

我需要能够查找并检索与每个键关联的布尔值。问题是字典的大小。在64位Linux系统上使用Python 2.7和上面的例子,根据sys.getsizeof(),dict的大小是3.1兆字节。 (每个条目大约56个字节,用于存储9位数加上一个布尔值)

我需要在dict中存储(大约)55.000个条目的布尔状态。每个dict键是一个9位整数。我尝试使用整数和str(theInteger)作为键,而不改变dict的大小。

我是否应该使用其他类型的数据结构或方法来保存具有如此大数据集的内存?

5 个答案:

答案 0 :(得分:8)

如果你用一个整数键查找你的布尔值,并且键的范围从0开始并且是连续的,那么就没有理由使用列表:

my_list = []
for n in range(56000):
        my_list[n] = True

或更好:

my_list = [True for n in range(5600])

如果这还不够,请尝试array模块并使用每个bool一个字节:

import array
my_array = array.array("b", (True for n in range(56000)))

如果这还不够好,请尝试bitarray module on PyPi

另一个想法是使用set:假设你有更多False而不是True,只需要一套:

my_true_numbers = {0, 2323, 23452} # just the True ones

并检查

value = number in my_true_numbers

如果您的True多于False,请反过来。

答案 1 :(得分:3)

Python: Reducing memory usage of dictionary接受的答案得出的结论是,你做的不多,我同意。字典的整体内存开销很小,但示例中键值对的数量会增加内存占用量。

可能有人认为可能:如果键始终是线性的,您可以直接创建布尔列表或更好地使用bitarray。然后,密钥将是隐含的。但如果这只是在你的例子中你不能做多少。

答案 2 :(得分:1)

为什么不使用巨大的位域? 您将数据编码为两位,因为您至少需要三个值:true,false和not_init / UB。使用的总内存为55.000*2 bits = 110 000 bits = 13 kBytes

A badly drawn diagram

set flag用于确保用户已正确设置该值(不必要),第二位包含该值。

使用64 bit unsigned integers,您只需要203个来存储整个数组。

然后你可以使用位索引访问它:假设你想要访问索引123的值。您需要访问位#246 ans #247(一个用于设置bool,另一个用于值)。

由于246247不如2**64,因此它们存储在first uint上。要访问它们:

return (( (1<<246) & array[0] ) >> 246 )

要访问任何位:

return (( (1<<n) & array[ n/(2**64) ] ) >> n)

(未测试位访问者)

设置一下:

array[ n/(2**64) ] = array[ n/(2**64) ] | (1<<n)

按位操作很棘手(算术移位与逻辑移位)并且不易调试,但它们可以非常强大。

答案 3 :(得分:1)

如果“找不到钥匙”对您来说不是一个重要的状态(即您可以将不在数组中的键视为False),您可以使用set代替仅存储元素映射到True。这需要大约30%的空间,因为每个条目只包含两个64位数量(散列和键)而不是三个量(散列,键,值)。

请注意,sys.getsizeof(dict)仅告诉您dict本身的大小,而不是其中包含的对象。创建56000 int作为键也将带有自己的成本:每个整数24个字节(类型指针,引用计数,值)。除了字典所占用的内存之外,这本身就达到了1.3MB。

要真正节省空间,可以使用NumPy 压缩稀疏行矩阵:

from scipy.sparse import lil_matrix # linked-list matrix, used to construct the csr matrix
vals = lil_matrix((1,1000000000), dtype='int8'))
# We will use 0 = no such key, 1 = True, 2 = False
for n in myIndices:
    vals[n] = 1
vals = vals.tocsr()

vals的内存使用量非常小:数据为56KB,索引为224KB,其他结构的内存使用量小于1KB。因此总大小小于 281KB (比dict小10倍),没有额外分配的整数。查看元素并更改非零元素非常快(在排序数组中进行二进制搜索),但插入新的非零值或将现有的非零值清零是昂贵的操作。

答案 4 :(得分:0)

根据您的需求,您可以使用列表来存储您的值。这只占字典使用空间的16%左右,但查找和插入等某些操作(可能很多)会慢一些。

values = list(range(56000))

如果您使用bisect模块并将值存储在排序列表中,则查找仍然比使用dict慢,但比天真x in my_list检查要快得多。

列表必须始终按排序顺序保存。要检查列表中是否有值,您可以使用此功能:

def is_in_list(values, x):
    i = bisect_left(values, x)
    return i != len(values) and values[i] == x

像这样工作:

>>> is_in_list([2, 4, 14, 15], 4)
True
>>> is_in_list([2, 4, 14, 15], 1)
False
>>> is_in_list([2, 4, 14, 15], 13)
False

此方法将显着减少内存使用量,但是 - compared to a dict or set - 查找需要O(log n)时间而不是O(1),插入需要O(n)而不是O(1)。