Skip to main content
 首页 » 编程设计

python之为什么我的 python 2.7 进程使用大量内存

2025年05月04日14落叶无声

考虑到列表只有 76 MB 长,我试图理解为什么这个 python 代码导致一个需要 236 MB 内存的进程。

import sys 
import psutil 
 
initial = psutil.virtual_memory().available / 1024 / 1024 
available_memory = psutil.virtual_memory().available 
 
vector_memory = sys.getsizeof([]) 
vector_position_memory = sys.getsizeof([1]) - vector_memory 
 
positions = 10000000 
 
print "vector with %d positions should use %d MB of memory " % (positions, (vector_memory + positions * vector_position_memory) / 1024 / 1024) 
print "it used %d MB of memory " % (sys.getsizeof(range(0, positions)) / 1024 / 1024) 
 
final = psutil.virtual_memory().available / 1024 / 1024 
 
print "however, this process used in total %d MB" % (initial - final) 

输出是:

vector with 10000000 positions should use 76 MB of memory  
it used 76 MB of memory  
however, this process used in total 236 MB 

增加 x10 个位置(即 positions = 100000000)会增加 x10 个内存。

vector with 100000000 positions should use 762 MB of memory  
it used 762 MB of memory  
however, this process used in total 2330 MB 

我的最终目标是尽可能多地吸收内存来创建一个很长的列表。为此,我创建了这段代码,以根据可用内存了解/预测我的列表有多大。令我惊讶的是,我猜 python 需要大量内存来管理我的列表。

为什么 python 占用这么多内存?!它在做什么?关于如何预测 python 的内存需求以有效地创建一个列表以使用几乎所有可用内存同时防止操作系统进行交换的任何想法?

请您参考如下方法:

getsizeof函数仅包括列表本身使用的空间。

但该列表实际上只是一个指向 int 对象的指针数组,您创建了 10000000 个,并且每个都占用内存——通常是 24 字节。

前几个数字(通常最多 255)是由解释器预先创建和缓存的,因此它们实际上是免费的,但其余的则不是。所以,你想添加这样的东西:

int_memory = sys.getsizeof(10000) 
 
print "%d int objects should use another %d MB of memory " % (positions - 256, (positions - 256) * int_memory / 1024 / 1024) 

然后结果会更有意义。


但请注意,如果您创建的不是具有 10M 个唯一整数的 range,而是 0-10000 中的 10M 个随机整数,或 0 的 10M 个副本,则该计算将不再是正确的。因此,如果要处理这些情况,您需要执行一些操作,例如隐藏您目前看到的每个对象的 id,并跳过对同一 id 的任何其他引用。

Python 2.x 文档曾经有一个指向执行此操作的旧递归 getsizeof 函数的链接,以及更多……但该链接已失效,因此已被删除。

The 3.x docs有一个链接到 a newer one ,这在 Python 2.7 中可能有效,也可能无效。 (我一眼就注意到它为 print 使用了一个 __future__ 语句,然后从 reprlib.repr 退回到 repr,所以它可能确实如此。)


如果您想知道为什么每个 int 都是 24 字节长(在 64 位 CPython 中;当然,它对于不同的平台和实现是不同的):

CPython 将每个内置类型表示为 C 结构,其中至少包含用于引用计数的空间和指向该类型的指针。对象需要表示的任何实际值都在该值之外。1 因此,最小的非单例类型每个实例将占用 24 个字节。


如果您想知道如何避免每个整数用完 24 个字节,答案是使用 NumPy's ndarray — 或者,如果由于某种原因你不能,stdlib 的 array.array .

任何一个都可以让你指定一个“原生类型”,比如 NumPy 的 np.int32 或 array.array 的 i,并创建一个直接包含 100M 个原生类型值的数组。这将需要每个值恰好 4 个字节,加上几十个常量字节的 header 开销,这比 list 的 8 个字节的指针小很多,最后还有一点松弛随长度缩放,加上一个 int 对象包裹每个值。

使用 array.array,您正在为空间牺牲速度,2 因为每次您想要访问其中一个值时,Python 都必须将其取出并将其“装箱”为 int 对象。

使用 NumPy,您将获得 速度和空间,因为 NumPy 允许您在经过严格优化的 C 循环中对整个数组执行矢量化操作。


<子> 1。您在 Python 中使用 class 创建的非内置类型呢?它们有一个指向字典的指针——您可以从 Python 领域将其视为 __dict__——它包含您添加的所有属性。因此,根据 getsizeof,它们是 24 字节,但当然您还必须添加该字典的大小。

<子> 2。除非你不是。防止您的系统进入交换 hell 可能比装箱和拆箱减慢速度更能加快速度。而且,即使您没有避开那个巨大的悬崖,您可能 仍然会避开涉及 VM 分页或缓存局部性的较小悬崖。