我在RAM中创建一个Python set
,其中包含1000万个10-char字符串(每个字符可以在0-255之间表示,不比这复杂,没有UTF8非ASCII复杂字符)。
我认为它应该使用大约~10M * 10 = 100 MB,数据结构可能+ 50%,即最大150 MB。但相反,该过程使用... 993 MB!
(作为比较,当我使用10个元素而不是10 * 1000 * 1000运行相同的脚本时,该过程仅在RAM中使用4 MB。)
如何制作一组10-char字符串的轻量级内存?
import random
def randstr(): # random string of length 10
return ''.join(random.choice('abcdefghijklmnopqrstruvwxyz') for i in range(10))
s = set()
for i in range(10*1000*1000):
s.add(randstr())
注意:如果可能,我希望保留set()
而不是其他数据库(如SQLite),因为会员查询非常快。
注意:我使用的是Python 2.7 64位
注意:在我的真实程序中,事实上它有1亿到5亿个项目,这就是我想节省内存的原因。
答案 0 :(得分:2)
标准集/字符串就是你得到的,因为Python中有很大的开销。但您可以使用不同的数据结构。在这里,我建议不仅要比原始数据占用更多空间,而且实际上需要 少空间而不是原始数据这是相当快速和简单的。
在之前的评论中,您说“我需要的唯一设置操作是成员资格查询"test" in s
”和“我希望每10000项目集的每次查找<50微秒”。
所以这是一个想法:将每个字符串拆分为prefix
长度为p
和suffix
(长度为10 - p
)。并将您的数据放入dict suffixes
映射前缀到空格分隔的后缀。例如,如果您使用p = 4
并且只有两个字符串"abcdefghij"
和"abcdklmnop"
,则您的字典将为{'abcd': 'efghij klmnop'}
。然后你用word[p:] in suffixes[word[:p]]
查看字符串。
我推荐p = 4
。那么你有26个 4 = 456976前缀和5亿个字符串,每个前缀有大约1094
个后缀。
拥有1000万个字符串和p = 4
:
25 MB for the dict itself
18 MB for the dict keys (i.e., the prefixes)
86 MB for the dict values (i.e., the suffixes)
130 MB total
拥有2000万字符串和p = 4
:
25 MB for the dict itself
18 MB for the dict keys (i.e., the prefixes)
156 MB for the dict values (i.e., the suffixes)
200 MB total
正如预期的那样,dict本身的大小及其keys =前缀没有改变,但values = suffix的大小增加了70 MB。应该如此,因为每个前缀增加七个字节(六个字母加一个空格),我们增加了一千万个。
<强> =&GT;对于5亿个字符串,预期为3560 MB ,每串7.12字节。所以即使少比你期望的每个字符串10个字节还多。
我无法测试5亿字,因此我使用n = 739645
和p = 2
并使用长度8作为随机字。这给了我739645/26 2 = 1094个后缀长度为每个前缀6,与完整测试相同。然后我测量了查找一百万个随机单词的时间。在repl.it(使用64位Python)上,每次查找需要12微秒。在我的PC上(我使用的是32位Python),每次查找需要2.1微秒。所以我估计你需要每次查找 5微秒,大约是你需要的10倍。
你可以通过不用空格分隔后缀而是使用CamelCase来使它更小更快。因此,对于上面的小例子,你有{'abcd': 'EfghijKlmnop'}
。那么每个字符串就有6.12个字节,查找速度应该提高7/6倍。
你也可以利用小字母表。有六个字母,你有26个 6 的可能性,只有log 256 (26 6 )≈3.53个字节。因此,您可以将每个六个字母的后缀转换为仅四个字节。 CamelCase不再工作了,但是通过空格分隔,你仍然只能为每个后缀存储5个字节,所以整个每串5.12个字节。与原始想法相比,已经转换后缀的查找速度应该提高7/5倍,尽管现在转换可能需要很长时间。
实际上,p = 5
可能会更好。它应该占用大约相同的内存量并且要快得多,因为除了1094个后缀之外,您只需要组合42个后缀,并且搜索这些后缀组字符串是大部分时间。对于5亿个字符串,我认为总大小将从3560 MB上升到大约4180.在我的PC上,查找平均 0.32微秒然后(使用739645长度为8的字符串并使用p = 3进行模拟)。由于五个字母可以容纳三个字节,因此您可以在密钥上保存1000 MB,在后缀上保存1000 MB,最终总计2180 MB,每个字符串 4.36字节。虽然转换为三字节字符串可能需要比实际查找更长的时间。您可以创建另一个存储转换的表,以便您可以查找它们,这将是快速的,但会再次花费额外的内存。另一个想法:keys =前缀可以从0到26 5 转换为 ints 。这应该可以节省大约200 MB,并且可能很快。
我用来生成上述输出的测试代码:
import random
from collections import defaultdict
from sys import getsizeof as sizeof
from timeit import timeit
# Settings: Number of strings and prefix length
n = 10*1000*1000
p = 4
def randstr(): # random string of length 10
return ''.join(random.choice('abcdefghijklmnopqrstruvwxyz') for i in range(10))
# Build and store the test data (n random strings)
suffixes = defaultdict(str)
for i in xrange(n):
word = randstr()
prefix, suffix = word[:p], word[p:]
suffixes[prefix] += suffix + ' '
# Show the sizes
dict_size = sizeof(suffixes)
keys_size = sum(map(sizeof, suffixes.keys()))
values_size = sum(map(sizeof, suffixes.values()))
print dict_size / 10**6, 'MB for the dict itself'
print keys_size / 10**6, 'MB for the dict keys (i.e., the prefixes)'
print values_size / 10**6, 'MB for the dict values (i.e., the suffixes)'
print (dict_size + keys_size + values_size) / 10**6, 'MB total'
# Speed test
words = [randstr() for _ in xrange(10**6)]
t = timeit(lambda: sum(word[p:] in suffixes[word[:p]] for word in words), number=1)
print '%.2f microseconds per lookup' % t
答案 1 :(得分:1)
十个字节的叮咬很短。因此开销很大。每个字符串占用47个字节。此外,每个set元素为哈希值和指针腾出空间,至少有24个字节,加上30%到50%的空闲条目,这是高效的哈希集。
您的号码:
至少830MB加上一些工作空间。 python无法让这些数字下降。