Python(或C)中的高效内存字符串到字符串映射
我需要一个内存高效的数据结构来存储大约一百万个键-值对,其中键是大约80字节的字符串,值是大约200字节的字符串,总键和值大小大约为280MB。我还需要按键高效地查找值,最好是哈希映射。内存开销应尽可能小,例如,对于280MB的有用数据,数据结构不应使用超过300MB的虚拟内存(包括Python(或C)中的高效内存字符串到字符串映射,python,data-structures,hash,map,memory-efficient,Python,Data Structures,Hash,Map,Memory Efficient,我需要一个内存高效的数据结构来存储大约一百万个键-值对,其中键是大约80字节的字符串,值是大约200字节的字符串,总键和值大小大约为280MB。我还需要按键高效地查找值,最好是哈希映射。内存开销应尽可能小,例如,对于280MB的有用数据,数据结构不应使用超过300MB的虚拟内存(包括malloc()开销和其他所有开销)。使用模式如下:我们从一个空的数据结构开始,逐步填充它,从不更改键,也从不更改值的长度。另外,数据结构可能支持更改值的长度,代价是100%的值开销(这意味着对于x值字节,x字节可能
malloc()
开销和其他所有开销)。使用模式如下:我们从一个空的数据结构开始,逐步填充它,从不更改键,也从不更改值的长度。另外,数据结构可能支持更改值的长度,代价是100%的值开销(这意味着对于x值字节,x字节可能会临时浪费在未使用的缓冲区空间中)
我需要一个纯Python模块,或者一个内置Python模块,或者一个最好带有(C)Python绑定的C实现。我更希望能够将整个数据结构序列化到磁盘,并非常快速地将其读回
为了证明这样小的开销是可能的,我创建了一个简单的设计,包含125万个元素的哈希表,其中包含指向1MB数据块的4字节指针,数据块包含如下键和值长度。这种设计有一个重要的限制:它不允许在不浪费内存区域的情况下删除或更改对。根据我对每个280字节的一百万个键值对的计算,开销小于3.6%(1080000字节)。上面的限制更宽泛,它们允许20000字节的开销
我刚刚发现,它提供了快速访问和内存高效的数据打包。我必须更仔细地检查它是否适合我的需要。您可以使用struct module打包二进制数据,并在需要时解包。 您可以使用这种方法实现内存效率高的存储。我想进入会很痛苦
- Martijn在一篇评论中提到了这一点(不确定人们为什么要用答案进行评论),但我同意:使用SQLite。你应该试一试,看看它是否能满足你的需要。你试过使用一个简单的dict吗?大部分数据都是字符串,因此开销可能符合您的要求。ApachePortableRuntime(又名APR)有一个基于c的哈希表。您可以在以下位置查看文档:
使用apr\u hash\t,您存储的所有内容都是无效的*。因此,它可以让您完全控制值。因此,如果需要,可以存储指向100字节块的指针,而不是字符串的实际长度。如果不打算进行大量删除,那么这并不难。删除会导致碎片 您还需要提交一个固定长度的密钥。你提到了80个字节。你的钥匙可以复制吗?如果没有,那就更容易了 这就是你要做的 您可以创建以下内容的数组:
struct {
char value[80];
char *data;
} key;
然后对数组进行排序
如果可以复制密钥,则需要:
struct link {
char *data;
link *next;
}
struct {
char value[80];
link *data;
} key;
(我的C已经生锈了,但这是它的要点)后者的每个键都指向一个值的链接列表
然后,查找是一个简单的二进制搜索。“痛苦”在于维护这个数组和插入/删除密钥。这并不像听起来那么痛苦,但它节省了大量内存,尤其是在64位系统上
您想要减少的是指针的数量。当你有很多充满指针的结构时,指针是昂贵的。在64位系统上,指针为8字节。因此,对于单个指针,您的内存预算为8MB
因此,费用是在构建阵列、复制和压缩内存(如果您“知道”您将有一百万行,并且可以提交给它,那么立即malloc(1000000*sizeof(key)),它将在扩展期间为您节省一些复制)
但不要害怕,一旦启动并运行,性能就相当好了。现代CPU实际上非常擅长复制100万个内存块
顺便说一句,我只是在Java中做了类似的事情。在64位JVM上,具有2500万个条目的映射是2G的RAM。我的解决方案(使用与此类似的技术)具有大约6亿的可用性。Java使用的指针比C多,但前提是相同的。您可以使用键的
sha1
,而不是键本身。如果键是唯一的,那么键的sha1
散列也可能是唯一的。它提供了一种节省内存的方法,可以在您的限制下勉强进入
from random import choice
from string import letters
from hashlib import sha1
def keygen(length):
return "".join(choice(letters) for _ in xrange(length))
def gentestdata(n=1000*1000):
# return dict((sha1(keygen(80)).digest(), keygen(200)) for _ in xrange(n))
d = {}
for _ in xrange(n):
key = sha1(keygen(80)).digest()
assert key not in d
value = keygen(200)
d[key] = value
return d
if __name__ == '__main__':
d = gentestdata()
在我的ubuntu设备上,它的最高内存为304 MB:
2010-10-26 14:26:02 hbrown@hbrown-ubuntu-wks:~$ ps aux | grep python
[...]
hbrown 12082 78.2 7.5 307420 303128 pts/1 S+ 14:20 4:47 python
够近了吗?这是python,不是C
稍后:另外,如果您的数据有点冗余,您可以
gzip
。这是一种时间与空间的权衡。好的,非常简单的方法
对数据结构使用python字典。我在python字典中填充了100万个随机键值对,其中键值为80个字符,值为200个字符。我的计算机占用了360844KB,这超出了您不超过300MB的规格,但我还是提供了一个解决方案,因为它仍然非常节省内存
这也不符合您拥有C API的要求。我不确定您为什么需要C,但由于问题被标记为Python,并且缺少C标记,我将提供纯Python,看看它是否符合要求
关于持久性。使用cPickle模块。它非常快,而且非常简单。要保存词典,请执行以下操作:
cPickle.dump(mydict, "myfile.pkl")
mydict = cPickle.load("myfile.pkl")
要重新加载词典,请执行以下操作:
cPickle.dump(mydict, "myfile.pkl")
mydict = cPickle.load("myfile.pkl")
第二个简单的想法是使用
shelve
模块,它基本上是基于磁盘的python字典。内存开销非常低(都在磁盘上)。但是它也慢得多。使用SQLite是个好主意。一个快速的实现可以告诉你是否足够快,不费吹灰之力
如果您决定自己动手,我建议您: 你的公关能力如何