Python:减less字典的内存使用

我试图加载一些文件到内存中。 这些文件有以下三种格式之一:

  • stringTAB int
  • stringTAB浮动
  • int TAB float。

事实上,他们是ngram静态文件,以防万一这有助于解决scheme。 例如:

i_love TAB 10 love_you TAB 12 

目前,我正在做的伪代码是

 loadData(file): data = {} for line in file: first, second = line.split('\t') data[first] = int(second) #or float(second) return data 

令我惊讶的是,虽然磁盘中的文件总大小约为21 MB,但在装入内存时,这个过程需要120 – 180 MB的内存! (整个python应用程序不会将任何其他数据加载到内存中)。

目前只有不到10个文件,其中大部分文件保持稳定,大约在5万到8万行,除了一个文件目前有数百万行。

所以我想问一个技术/数据结构来减less内存消耗:

  • 任何压缩技术的build议?
  • 如果我仍然使用字典,有什么办法来减less内存? 是否有可能像Java中的Python字典中设置“加载因子”?
  • 如果你有其他一些数据结构,“也愿意交易一些速度来减less内存。 不过,这是一个时间敏感的应用程序,所以一旦用户input他们的查询,我认为花费超过几秒的时间来返回结果是不合理的。 关于这一点,我仍然对谷歌如何快速完成谷歌翻译感到惊讶:他们必须使用大量的技术和大量的服务器function。

非常感谢你。 我期待你的build议。

我不能提供一个完整的策略来帮助改善内存占用,但我相信这可能有助于分析究竟是什么花了太多的内存。

如果你看一下Python的字典实现 (这是一个相对直接的哈希表实现),以及内置的string和整型数据types的实现,例如在这里 (具体来说是object.h,intobject .h,stringobject.h和dictobject.h以及../Objects中相应的* .c文件),可以以一定的精度计算预期的空间要求:

  1. 一个整数是一个固定大小的对象,即它包含一个引用计数,一个types指针和实际的整数,一般 32位系统上至less有12个 字节 ,在64位系统上至less24个字节 ,没有考虑到额外的空间失去对准。

  2. 一个string对象是可变大小的,这意味着它包含

    • 引用计数
    • types指针
    • 大小信息
    • 用于懒散计算的哈希码的空间
    • 状态信息(例如用于实习string)
    • 一个指向dynamic内容的指针

    总共 32位上至less有24个字节或在64位上有60个字节不包括string本身的空间。

  3. 字典本身由多个桶组成,每个桶包含

    • 当前存储的对象的散列码(由于所使用的冲突解决策略而不能从桶的位置预测)
    • 一个指向关键对象的指针
    • 一个指向值对象的指针

    总共 32位上至less有12个 字节,在64位上至less有 24个字节

  4. 字典开始时有8个空桶 ,每当容量达到时,就通过将条目数加倍调整字典大小

用一个46,461个唯一的string(337,670字节串联的string大小)的列表进行testing ,每个string都与一个32位机器上的一个整数相关联 – 与您的设置类似。 根据上面的计算,我期望最小的内存占用

  • 46,461 *(24 + 12)字节= 1.6 MB为string/整数组合
  • 337,670 = 0.3 MB的string内容
  • 65,536 * 12字节= 1.6 MB的散列桶(resize13次后)

共2.65 MB。 (对于64位系统,相应的计算结果为5.5 MB。)

当运行Python解释器空闲时,根据ps -tool的占用空间为4.6 MB。 因此,创build字典后预期的总内存消耗约为4.6 + 2.65 = 7.25 MB。 我testing的真实内存占用(根据ps7.6 MB。 我想这个额外的约。 Python的内存分配策略产生的开销消耗了0.35 MB(对于内存竞技场等)

当然很多人现在会指出,我使用ps来衡量内存占用是不准确的,我对32位和64位系统上指针types和整数大小的假设在许多特定的系统上可能是错误的。 理所当然的。

但是,我认为, 关键的结论是:

  • Python 字典实现消耗了令人惊讶的less量内存
  • 但是,对于引用计数,预先计算的哈希码等,许多int和(特别是) string对象所占用的空间比你想象的要多
  • 几乎没有办法避免内存开销 ,只要你使用Python,并希望string和整数表示为单个对象 – 至less我不明白怎么做
  • 寻找(或者实现你自己)一个Python-C扩展是值得的,它实现了一个将键和值存储为C指针(而不是Python对象)的哈希。 我不知道这是否存在; 但我相信这可以做到,可以减less一半以上的内存占用。

1)内存中的SQLite听起来是一个很好的解决scheme,它会让你更容易地查询你的数据,一旦它被加载,这是一个乐趣

sqlite3.connect( ':存储器:')

2)你可能想要一个命名的元组 – 我敢肯定他们比词典更轻,你可以使用点符号(为此我有一个审美偏好无论如何)访问属性。

http://docs.python.org/dev/library/collections

3)你可能想看看Redis: https : //github.com/andymccurdy/redis-py

它是快速的,可以让你轻松地坚持事情,这意味着你不必每次都要加载整个集合。

4)一个听起来像一个好主意,但增加了一些理论上的复杂性,在你的工作结束。 您可以使用Redis来实现和存储它,这将进一步提高您的速度。

但总的来说,命名元组可能是这里的伎俩。

在磁盘中你只有string,当加载到Python时,解释器必须为每个string和每个字典创build一个完整的结构,除了string本身。

没有办法减less字典使用的内存,但还有其他的方法来解决这个问题。 如果你愿意交易一些内存的速度,你应该考虑从SQLite文件存储和查询string,而不是将所有内容加载到内存中的字典。

听起来像Trie( http://en.m.wikipedia.org/wiki/Trie )数据结构可能更适合您的记忆效率的需求。

更新:python字典的内存效率已被提出作为一个问题,虽然它被拒绝从标准库鉴于第三方库的可用性。 请参阅: http : //bugs.python.org/issue9520

你可以用bist.sorteddict代替dict 来进行log(n)访问,而不会增加内存开销。 这很方便,因为它的行为完全像一个字典,即它实现了它的所有方法,所以你只需要改变初始types。

如果你试图在Python中紧凑地存储数字数据,你最好的解决scheme可能是Numpy。

Numpy( http://numpy.org )使用本地C结构分配数据结构。 它的大部分数据结构都假定你正在存储一个数据types,所以这并不适用于所有的情况(你可能需要存储空值等),但它可以是非常非常快速的,你可以问。 很多科学都可以完成(另请参见:SciPy)。

当然,还有另一个选项: zlib ,如果你有:

  • 充足的CPU周期和
  • 大量的数据将不适合内存

你可以只声明一个数据的“页面”(不pipe你想要的大小),读取数据,将数据存储在数组中,压缩数据,然后读入更多的数据,直到你将所有的数据存储在内存中想。

然后,迭代页面,解压缩,转换回数组,并根据需要执行操作。 例如:

 def arrayToBlob(self, inArray): a = array.array('f', inArray) return a.tostring() def blobToArray(self, blob, suppressWarning=False): try: out = array.array('f', []) out.fromstring(blob) except Exception, e: if not suppressWarning: msg = "Exception: blob2array, err: %s, in: %s" % (e, blob) self.log.warning(msg) raise Exception, msg return out 

一旦你有数据作为blob,你可以将这个blob传递给zlib并压缩数据。 如果你有很多重复的值,这个blob可以被大大压缩。

当然,这比保留所有的未压缩要慢,但是如果你不能把它放在内存中,你的select是有限的。

即使压缩,也可能不适合内存,此时可能需要将压缩的页面写成string或泡菜等。

祝你好运!