52

我正在尝试将几个文件加载到内存中。这些文件具有以下 3 种格式之一:

  • 字符串制表符 int
  • 字符串制表符浮动
  • int 制表符浮动。

实际上,它们是 ngram 静态文件,以防这有助于解决方案。例如:

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 个,大多数会保持稳定在 50-80k 行左右,除了一个文件目前有数百万行。

所以我想要求一种技术/数据结构来减少内存消耗:

  • 对压缩技术有什么建议吗?
  • 如果我仍然使用dict,有什么办法可以减少内存?是否可以像在 Java 中为 Python dict 设置“负载因子”?
  • 如果你有一些其他的数据结构,'我也愿意牺牲一些速度来减少内存。然而,这是一个时间敏感的应用程序,所以一旦用户输入他们的查询,我认为花费超过几秒钟的时间来返回结果是不太合理的。关于这一点,我仍然对谷歌如此迅速地完成谷歌翻译感到惊讶:他们一定是使用了很多技术+大量服务器的力量?

非常感谢你。我期待您的建议。

4

6 回答 6

84

我无法提供有助于改善内存占用的完整策略,但我相信它可能有助于分析究竟是什么占用了这么多内存。

如果您查看字典的Python 实现(这是哈希表的相对直接的实现),以及内置字符串和整数数据类型的实现,例如这里(特别是 object.h、intobject .h、stringobject.h 和 dictobject.h,以及 ../Objects 中对应的 *.c 文件),您可以准确地计算出预期的空间需求:

  1. 整数是一个固定大小的对象,即它包含一个引用计数、一个类型指针和实际的整数,总共通常32 位系统上至少 12 个字节,在 64 位系统上至少 24 个字节,不考虑可能的额外空间通过对齐丢失。

  2. 字符串对象是可变大小的,这意味着它包含

  • 参考计数

  • 类型指针

  • 尺寸信息

  • 延迟计算的哈希码的空间

  • 状态信息(例如用于内部字符串

  • 指向动态内容的指针

    总共至少 24 字节在 32 位或60 字节在 64 位,不包括字符串本身的空间。

  1. 字典本身由许多桶组成,每个桶包含
  • 当前存储的对象的哈希码(由于使用的冲突解决策略,无法从桶的位置预测)

  • 指向关键对象的指针

  • 指向值对象的指针

    在32 位上总共至少 12 个字节,在 64 位上总共至少24 个字节。

  1. 字典从8 个空桶开始,每当达到其容量时,通过将条目数加倍来调整大小。

我使用 46,461 个唯一字符串(337,670 字节串联字符串大小)的列表进行了测试,每个字符串都与一个整数相关联 — 类似于您在 32 位机器上的设置。根据上面的计算,我预计最小内存占用为

  • 字符串/整数组合的 46,461 * (24+12) 字节 = 1.6 MB
  • 337,670 = 0.3 MB 用于字符串内容
  • 65,536 * 12 字节 = 1.6 MB 用于哈希桶(调整大小 13 次后)

总共 2.65 MB。(对于 64 位系统,相应的计算产生 5.5 MB。)

当运行 Python 解释器空闲时,根据ps-tool 其占用空间为 4.6 MB。因此,创建字典后的预期总内存消耗约为 4.6 + 2.65 = 7.25 MB。在我的测试中,真正的内存占用(根据ps)是7.6 MB。我猜额外的约。通过 Python 的内存分配策略(用于内存领域等)产生的开销消耗了 0.35 MB

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

但是,我相信,关键结论是:

  • Python字典实现消耗的内存非常
  • 但是许多int和(特别是)字符串对象占用的空间,用于引用计数、预先计算的哈希码等,比你最初想象的要多
  • 几乎没有办法避免内存开销,只要您使用 Python 并希望将字符串和整数表示为单个对象 - 至少我不知道如何做到这一点
  • 寻找(或自己实现)一个Python-C 扩展可能是值得的,它实现了一个将键和值存储为 C 指针(而不是 Python 对象)的哈希。我不知道那是否存在;但我相信这是可以做到的,并且可以将内存占用减少一半以上。
于 2012-04-22T05:11:57.347 回答
8

1) 内存中的 SQLite 听起来是一个很好的解决方案,它可以让您在加载数据后更轻松地查询数据,这是一种乐趣

sqlite3.connect(':memory:')

2)您可能想要一个命名元组 - 我很确定它们比字典更轻,您可以使用点表示法访问属性(无论如何我都有审美偏好)。

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

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

它速度很快,可以让你轻松地坚持下去,这意味着你不必在每次想要使用它时都加载整个集合。

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

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

于 2013-08-22T14:56:27.710 回答
6

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

没有办法减少 dicts 使用的内存,但是还有其他方法可以解决这个问题。如果您愿意以速度换取内存,您应该考虑从 SQLite 文件存储和查询字符串,而不是将所有内容加载到内存中的字典中。

于 2012-04-22T03:31:11.280 回答
4

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

更新:python dict 的内存效率已被提出作为一个问题,尽管考虑到第三方库的可用性,它被标准库拒绝。见:http ://bugs.python.org/issue9520

于 2012-04-22T05:53:00.913 回答
3

您可以将 dict 替换为blist.sorteddict以进行 log(n) 访问,而不会产生内存开销。它很方便,因为它的行为与字典完全一样,即它实现了它的所有方法,因此您只需更改初始类型。

于 2013-10-29T18:47:17.533 回答
3

如果您试图将数字数据紧凑地存储在内存中的 python 中,那么您最好的解决方案可能是 Numpy。

Numpy ( http://numpy.org ) 使用原生 C 结构分配数据结构。它的大多数数据结构都假定您存储的是单一数据类型,所以这并不适用于所有情况(您可能需要存储 null 等),但它可以非常、非常、非常快,并且与你可以要求。很多科学都用它完成了(另见:SciPy)。

当然,还有另一种选择: zlib,如果你有:

  • 充足的 CPU 周期,以及
  • 大量无法放入内存的数据

您可以将数据的“页面”(无论您想要多大)声明为一个数组,读入数据,将其存储在数组中,压缩它,然后读入更多数据,直到您将所有数据都保存在内存中想。

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

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 可以被大大压缩。

当然,它比保持全部未压缩要慢,但是如果您无法将其放入内存中,那么您的选择一开始就受到限制。

即使使用压缩,它也可能无法全部放入内存,此时您可能必须将压缩页面写成字符串或泡菜等。

祝你好运!

于 2014-12-01T17:10:12.610 回答