Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/python/288.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Python(或C)中的高效内存字符串到字符串映射_Python_Data Structures_Hash_Map_Memory Efficient - Fatal编程技术网

Python(或C)中的高效内存字符串到字符串映射

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字节可能

我需要一个内存高效的数据结构来存储大约一百万个键-值对,其中键是大约80字节的字符串,值是大约200字节的字符串,总键和值大小大约为280MB。我还需要按键高效地查找值,最好是哈希映射。内存开销应尽可能小,例如,对于280MB的有用数据,数据结构不应使用超过300MB的虚拟内存(包括
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是个好主意。一个快速的实现可以告诉你是否足够快,不费吹灰之力


      如果您决定自己动手,我建议您:

      你的公关能力如何