eryksun 已经回答了问题 #1,我已经回答了问题 #3(原来的 #4),但现在让我们回答问题 #2:
为什么它特别释放 50.5mb - 释放的数量是多少?
它所依据的最终是 Python 内部的一系列巧合,这些巧合malloc
很难预测。
首先,根据您测量内存的方式,您可能只测量实际映射到内存中的页面。在这种情况下,任何时候页面被分页器换出,内存都会显示为“已释放”,即使它还没有被释放。
或者您可能正在测量正在使用的页面,这些页面可能会或可能不会计算已分配但从未触及的页面(在乐观过度分配的系统上,如 linux)、已分配但已标记的页面MADV_FREE
等。
如果您真的在测量分配的页面(这实际上不是一件非常有用的事情,但它似乎是您要问的),并且页面确实已被释放,那么可能发生这种情况的两种情况:要么你'已经使用brk
或等效于缩小数据段(现在非常罕见),或者您已经使用munmap
或类似的方式来释放映射段。(理论上,后者还有一个较小的变体,因为有一些方法可以释放映射段的一部分——例如,MAP_FIXED
为MADV_FREE
您立即取消映射的段窃取它。)
但是大多数程序不会直接从内存页面中分配东西。他们使用malloc
-style 分配器。当您调用 时free
,如果您恰好是free
映射中的最后一个活动对象(或数据段的最后 N 个页面),分配器只能将页面释放到操作系统。您的应用程序无法合理地预测这一点,甚至无法提前检测到它发生了。
CPython 使这变得更加复杂——它在malloc
. (有关更详细的解释,请参阅源注释。)最重要的是,即使在 C API 级别,更不用说 Python,您甚至无法直接控制何时释放顶级对象。
那么,当你释放一个对象时,你怎么知道它是否会释放内存给操作系统呢?嗯,首先你必须知道你已经释放了最后一个引用(包括你不知道的任何内部引用),允许 GC 释放它。(与其他实现不同,至少 CPython 将在允许时立即释放对象。)这通常会在下一层释放至少两件事(例如,对于字符串,您正在释放PyString
对象,以及字符串缓冲区)。
如果你确实释放了一个对象,要知道这是否会导致下一级释放一个对象存储块,你必须知道对象分配器的内部状态,以及它是如何实现的。(除非你释放块中的最后一个东西,否则它显然不会发生,即使那样,它也可能不会发生。)
如果你确实释放了一个对象存储块,要知道这是否会导致free
调用,你必须知道 PyMem 分配器的内部状态,以及它是如何实现的。(同样,您必须在malloc
ed 区域内释放最后一个正在使用的块,即使那样,它也可能不会发生。)
如果你做 free
一个malloc
ed 区域,要知道这是否会导致一个munmap
或等效(或brk
),你必须知道 的内部状态malloc
,以及它是如何实现的。与其他的不同,这个是高度特定于平台的。(同样,您通常必须解除分配段中最后一个正在使用malloc
的mmap
,即使那样,它也可能不会发生。)
所以,如果你想了解为什么它恰好释放了 50.5mb,你将不得不从下往上追踪它。为什么在您进行一次或多次调用时(可能超过 50.5mb)malloc
取消映射 50.5mb 的页面?free
您必须阅读平台的malloc
,然后遍历各种表格和列表以查看其当前状态。(在某些平台上,它甚至可能使用系统级信息,如果不制作系统快照以进行离线检查,几乎不可能捕获这些信息,但幸运的是,这通常不是问题。)然后你必须在上面的 3 个级别上做同样的事情。
所以,这个问题唯一有用的答案是“因为”。
除非您正在进行资源有限(例如,嵌入式)开发,否则您没有理由关心这些细节。
而如果你在做资源有限的开发,知道这些细节是没有用的;您几乎必须围绕所有这些级别进行最终运行,特别mmap
是在应用程序级别所需的内存(可能在两者之间使用一个简单的、易于理解的、特定于应用程序的区域分配器)。