Python内存优化技巧

13

我需要优化我的应用程序的RAM使用率。
请不要劝我在编写Python代码时不必关注内存问题。我使用非常大的默认字典所以存在内存问题(是的,我也希望代码快一些)。目前我的内存占用量为350MB,并且还在增长。我已经无法使用共享主机,如果我的Apache打开更多进程,内存会增加两到三倍……而这是很昂贵的。
我进行了广泛的剖析,并且知道我的问题出在哪里。
我有几个大字典(> 100K条目)并且键是Unicode。一个字典从140字节开始并快速增长,但更大的问题在于键。Python优化内存中的字符串(或者我读过这样的说法),以便查找可以是ID比较。("interning"它们)。不确定这对于unicode字符串也适用(我无法将它们“intern”)。
存储在字典中的对象是元组列表(一个对象,一个整数,一个整数)。

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

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

Python 2.6.5在Linux(生产)和Windows上都可以使用。 有没有关于优化字典/列表/元组内存使用的技巧? 我甚至想使用C语言编写——我不在乎这个非常小的代码片段是否丑陋。它只是一个单一的位置。

提前感谢!


两个小评论:我真的很喜欢开箱即用的系统级答案,但是它们(例如数据库,即使缓存)的性能真的可以与Python字典相比吗?我正在运行实时算法,而字典几乎足够快。我肯定会尝试memcached和Redis(很酷),但进程间通信对我来说是否足够快?(抱歉现在才添加这个。很难同时优化内存和速度...) 此外,我的字典大多是只读的。我能以某种方式利用这个知识吗? - Tal Weiss
1
"PEP 412: Key-Sharing Dictionary" 可能对你有兴趣。我相信它被包括在 Python 3.3 中。http://www.python.org/dev/peps/pep-0412/ - bcoughlan
@bcoughlan 非常酷,谢谢!不幸的是,我需要等待2.7的回溯。 - Tal Weiss
你必须使用CPython吗?还是可以使用PyPy(它对集合类型有一些非常聪明的优化,并且可以JIT编译为本地代码...) - snim2
我不必做/使用任何东西。您认为 PyPy 有机会在内存和速度方面提供相同或更好的性能吗? - Tal Weiss
7个回答

13
我建议以下做法:将所有值存储在数据库中,并保持一个内存字典,以字符串哈希作为键。如果发生冲突,则从数据库中获取值,否则(绝大多数情况下)使用字典。实际上,这将是一个巨大的缓存。
Python中字典的一个问题是它们占用了很多空间:即使是int-int字典在32位系统上每个键值对也使用45-80字节。同时,array.array('i')仅使用8字节每对整数,通过一些簿记工作,可以实现一个相当快速的基于数组的int→int字典。
一旦你有了一个内存高效的int-int字典实现,将你的string → (object, int, int)字典分成三个字典,并使用哈希而不是完整的字符串。您将获得一个int → object和两个int → int字典。如下所示模拟int → object字典:保留对象列表并将对象的索引存储为int → int字典的值。
我知道为了得到一个基于数组的字典需要涉及相当数量的编码。我曾经遇到过类似你的问题,并实现了一个相当快速、非常节省内存的通用哈希整数字典。这是我的代码(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 在读写时引发CollisionErrorCHK_DELETING 在读取时返回None,在写入时保持沉默,CHK_IGNORING 不进行碰撞检查。

接下来是我的实现简要描述,欢迎优化提示!顶层数据结构是常规数组字典。每个数组包含多达2^16 = 65536个整数对(2^32的平方根)。键k和相应的值v都存储在第k/65536个数组中。这些数组按需初始化并按键排序。每次读取和写入都执行二进制搜索。碰撞检查是一个选项。如果启用,则尝试覆盖已经存在的键将从字典中删除该键和关联的值,将该键添加到一组冲突键中,并(再次选择性地)引发异常。


4

对于 Web 应用程序,您应该使用数据库,您现在的做法是为每个 Apache 进程创建一个 dict 的副本,这极其浪费资源。如果服务器上有足够的内存,则数据库表将被缓存在内存中(如果没有足够的内存来存储表的一个副本,请将更多 RAM 放入服务器)。只需记得在数据库表上放置正确的索引,否则性能会受到影响。


2

我曾遇到这样的情况,需要对一组大对象按照多个元数据属性进行排序和过滤。由于不需要它们中的大部分,所以我将它们存储到磁盘上。

考虑到你的数据类型非常简单,使用SQLite数据库可能是解决所有问题并加速处理速度的快捷方式。


1

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


1

如果你想继续使用内存数据存储,可以尝试使用memcached

这样,你就可以从所有的Python进程中使用单个内存键/值存储。

有几个Python memcached客户端库可供选择。


3
Memcached是有损的,因此不适合用作数据存储。 - Ignacio Vazquez-Abrams

1

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

我每天都在使用它进行数字计算,同时也在生产系统中用作数据存储,并且非常推荐它。

此外,您是否有将应用程序代理到 nginx 而不是使用 Apache 的选项?如果允许,您可能会发现这种代理/ Web 应用程序安排对资源的需求较小。

祝你好运。


0

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


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接