【问题标题】:Python tips for memory optimization内存优化的 Python 技巧
【发布时间】:2010-06-11 08:22:46
【问题描述】:

我需要优化我的应用程序的 RAM 使用率。
请不要让我讲课告诉我在编写 Python 时不应该关心内存。我有一个内存问题,因为我使用了非常大的默认字典(是的,我也想快点)。我目前的内存消耗是 350MB 并且还在增长。我已经无法使用共享主机,如果我的 Apache 打开更多进程,内存会翻倍和三倍……而且价格昂贵。
我已经完成了广泛的分析,并且我确切地知道我的问题出在哪里。
我有几个带有 Unicode 键的大型(>100K 条目)字典。字典从 140 字节开始并且增长很快,但更大的问题是键。 Python 优化内存中的字符串(或者我已经读过),以便查找可以是 ID 比较(“实习”它们)。不确定对于 unicode 字符串也是如此(我无法“实习”它们)。
存储在字典中的对象是元组列表(an_object, an int, an int)。

my_big_dict[some_unicode_string].append((my_object, an_int, another_int))

我已经发现值得拆分为多个字典,因为元组占用大量空间...
我发现在将字符串用作键之前,我可以通过对字符串进行散列来节省 RAM! 但不幸的是,我在 32 位系统上遇到了生日冲突。 (附带问题:有没有可以在 32 位系统上使用的 64 位密钥字典?)

Linux(生产)和 Windows 上的 Python 2.6.5。 关于优化字典/列表/元组的内存使用的任何提示? 我什至考虑过使用 C - 我不在乎这段非常小的代码是否丑陋。它只是一个奇异的位置。

提前致谢!

【问题讨论】:

  • 2 个小 cmets:我真的很喜欢开箱即用的系统级答案,但是它们(例如数据库,即使缓存)真的可以在性能上与 Python 字典相提并论吗?我正在运行实时算法,而字典几乎不够快。我肯定会尝试 memcached 和 Redis(很酷),但是进程间通信对我来说足够快吗? (很抱歉现在添加这个。很难优化内存和速度......)另外,我的字典大多是只读的。我可以以某种方式使用这些知识吗?
  • “PEP 412:密钥共享词典”您可能会感兴趣。我相信它包含在 Python 3.3 python.org/dev/peps/pep-0412
  • @bcoughlan 非常酷,谢谢!可悲的是,我需要等待 2.7 的反向移植。
  • 你必须使用 CPython 还是可以使用 PyPy(它对集合类型进行了一些非常聪明的优化,并且 JIT 编译为原生...)
  • 我不需要做/使用任何东西。您认为 PyPy 有机会在内存和速度方面提供相同或更好的性能吗?

标签: python optimization memory-management


【解决方案1】:

我建议如下:将所有值存储在数据库中,并保留一个以字符串哈希作为键的内存字典。如果发生冲突,请从数据库中获取值,否则(绝大多数情况下)使用字典。实际上,它将是一个巨大的缓存。

Python 中字典的一个问题是它们占用了大量空间:即使是 int-int 字典,在 32 位系统上每个键值对也使用 45-80 字节。同时,array.array('i') 每对 int 只使用 8 个字节,并且通过一点点记账就可以实现一个相当快的基于数组的 int → int em> 字典。

一旦你有一个 int-int 字典的内存高效实现,将你的 string → (object, int, int) 字典分成三个字典,并使用散列而不是完整的字符串。您将获得一个 int → object 和两个 int → int 字典。如下模拟 int → object 字典:保留对象列表并将对象的索引存储为 int → int 字典的值。

我确实意识到要获得基于数组的字典需要进行大量编码。我遇到了与您类似的问题,并且我已经实现了一个相当快、非常节省内存的通用 hash-int 字典。 Here's my code(BSD 许可证)。它是基于数组的(每对 8 个字节),它负责密钥散列和冲突检查,它在写入期间保持数组(实际上是几个较小的数组)有序,并在读取时进行二进制搜索。您的代码简化为:

dictionary = HashIntDict(checking = HashIntDict.CHK_SHOUTING)
# ...
database.store(k, v)
try:
    dictionary[k] = v
except CollisionError:
    pass
# ...
try:
    v = dictionary[k]
except CollisionError:
    v = database.fetch(k)

checking 参数指定发生冲突时会发生什么:CHK_SHOUTING 在读取和写入时引发 CollisionError,CHK_DELETING 在读取时返回 None,在写入时保持沉默,CHK_IGNORING 不会碰撞检查。

以下是我的实现的简要说明,欢迎提出优化提示!顶级数据结构是一个常规的数组字典。每个数组最多包含2^16 = 65536 整数对(2^32 的平方根)。一个键k 和一个对应的值v 都存储在k/65536-th 数组中。数组按需初始化并按键排序。在每次读取和写入时执行二进制搜索。碰撞检查是一种选择。如果启用,覆盖现有键的尝试将从字典中删除键和关联值,将键添加到一组冲突键中,并(同样,可选地)引发异常。

【讨论】:

    【解决方案2】:

    对于 Web 应用程序,您应该使用数据库,而您这样做的方式是为每个 apache 进程创建一个 dict 副本,这非常浪费。如果服务器上有足够的内存,则数据库表将缓存在内存中(如果您没有足够的内存来存储一份表,请在服务器中放入更多 RAM)。请记住在您的数据库表上放置正确的索引,否则您的性能会很差。

    【讨论】:

      【解决方案3】:

      我曾经遇到过一些大型对象的集合,我需要根据多个元数据属性通过不同的方法对这些对象进行排序和过滤。我不需要它们的较大部分,所以我将它们转储到磁盘。

      由于您的数据类型如此简单,一个快速的 SQLite 数据库可能会解决您的所有问题,甚至可以加快速度。

      【讨论】:

        【解决方案4】:

        如果您想保留内存中的数据存储,可以尝试memcached 之类的方法。

        这样,您可以使用来自所有 Python 进程的单个内存键/值存储。

        有几个 python memcached 客户端库。

        【讨论】:

        • memcached 有损,因此不适合数据存储。
        【解决方案5】:

        Redis 如果您可以选择在共享主机上使用它,这将是一个不错的选择 - 类似于 memcached,但针对数据结构进行了优化。 Redis 还支持 python 绑定。

        我每天都使用它来进行数字运算,而且在生产系统中也将它用作数据存储,我不能高度推荐它。

        另外,您是否可以选择在 nginx 后面代理您的应用程序而不是使用 Apache?您可能会发现(如果允许)这种代理/webapp 安排对资源的消耗较少。

        祝你好运。

        【讨论】:

          【解决方案6】:

          使用shelve 或数据库来存储数据,而不是使用内存中的字典。

          【讨论】:

            【解决方案7】:

            如果您想进行广泛的优化并完全控制内存使用,您还可以编写一个 C/C++ 模块。使用Swig 可以轻松地将代码包装到 Python 中,与纯 C Python 模块相比,性能开销很小。

            【讨论】:

              猜你喜欢
              • 2010-11-10
              • 1970-01-01
              • 1970-01-01
              • 2013-01-10
              • 1970-01-01
              • 2011-04-30
              • 2012-08-30
              • 1970-01-01
              • 2011-11-01
              相关资源
              最近更新 更多