繁体   English   中英

Python中的10-char字符串集合的RAM大小是预期的10倍

[英]Set of 10-char strings in Python is 10 times bigger in RAM as expected

我在RAM中创建一个包含10千万个10-char字符串的Python set (每个字符可以在0-255之间表示,不比这复杂,没有UTF8非ASCII复数字符)。

我认为它应该使用大约~10M * 10 = 100 MB,数据结构可能+ 50%,即最大150 MB。 但相反,该过程使用... 993 MB!

(作为比较,当我使用10个元素而不是10 * 1000 * 1000运行相同的脚本时,该过程仅在RAM中使用4 MB。)

如何制作一个10-char字符串的轻量级RAM?

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亿个项目,这就是我想节省内存的原因。

使用标准集/字符串就是你得到的,因为Python中有很大的开销。 但您可以使用不同的数据结构。 在这里我提出一个不仅不采取更多的空间比你的原始数据,但实际花费更少的空间比原始数据 ,这是非常快速和简单。

新数据结构

在之前的评论中,您说“我需要的唯一设置操作是"test" in s成员资格查找"test" in s “和”我期望每次查找100M项目集<50微秒“。

所以这是一个想法:将每个字符串拆分为长度为pprefixsuffix (长度为10- p )。 并将您的数据放入dict suffixes将前缀映射到以空格分隔的后缀。 因此,例如,如果您使用p = 4并且只有两个字符串"abcdefghij""abcdklmnop" ,那么您的dict将是{'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。 应该如此,因为每个前缀增加七个字节(六个字母加一个空格),我们增加了一千万个。

=>对于5亿个字符串,期望3560 MB ,即每个字符串7.12个字节 所以甚至少于你期望的每个字符串10个字节。

时间

我无法测试5亿字,所以我使用n = 739645p = 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.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个字节 虽然转换为三字节字符串可能需要比实际查找更长的时间。 您可以创建另一个存储转换的表,以便您可以查找它们,这将是快速的,但会再次花费额外的内存。 另一个想法:该键=前缀可以被转换为整数 0〜26 5。 这应该节省大约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

十字节的叮咬非常短。 因此开销很大。 每个字符串占用47个字节。 此外,每个set元素为哈希值和指针腾出空间,至少有24个字节,加上30%到50%的空闲条目,这是高效哈希集所需要的。

对于你的号码:

  • 字符串为470MB
  • 哈希240MB
  • 120MB免费房间

至少830MB加上一些工作空间。 python无法将这些数字降下来。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM