python管理大变量分配/释放的策略是什么?



作为这个问题的后续,似乎在(C)Python中对于小变量和大变量有不同的分配/解除分配策略。
更准确地说,对象大小中似乎有一个边界,超过该边界,分配的对象使用的内存可以返回给操作系统。 低于此大小,内存不会返回给操作系统。

引用从 Numpy 释放内存策略中获取的答案:

例外情况是,对于大型单个分配(例如,如果您创建多兆字节数组),将使用不同的机制。如此大的内存分配可以释放回操作系统。因此,可能特别是程序中的非numpy部分产生了您看到的问题。

事实上,这两种配置策略很容易表现出来。例如:

  • 第一种策略:不将内存返回给操作系统
import numpy as np
import psutil
import gc
# Allocate  array
x = np.random.uniform(0,1, size=(10**4))
# gc
del x
gc.collect()
# We go from 41295.872 KB to 41295.872 KB
# using psutil.Process().memory_info().rss / 10**3; same behavior for VMS

=> 没有内存返回给操作系统

  • 第二种策略:释放的内存返回给操作系统

当做同样的实验,但使用更大的数组时:

x = np.random.uniform(0,1, size=(10**5))
del x
gc.collect()
# We go from 41582.592 KB to 41017.344 KB

=> 内存释放到操作系统

似乎使用第二种策略分配大约大于8*10**4字节的对象。

所以:

  • 是否记录了此行为?(分配策略变化的确切边界是什么?
  • 这些策略的内部结构是什么(不仅仅是假设使用mmap/munmap将内存释放回操作系统)
  • 这是 100% 由 Python 运行时完成的,还是 Numpy 有特定的处理方式?(numpy 文档提到了在内存分配器之间切换的NPY_USE_PYMEM)

你观察到的不是CPython的策略,而是你的CPython版本正在使用的C运行时附带的内存分配器策略。

当CPython通过malloc/free分配/解除分配内存时,它不会直接与底层操作系统通信,而是与内存分配器的具体实现通信。就我在Linux上而言,它是GNU Allocator。

GNU 分配器有不同的所谓的竞技场,其中内存不会返回给操作系统,而是保留,以便可以重复使用而无需与操作系统通信。但是,如果请求大量内存(无论"大"的定义如何),分配器不会使用来自竞技场的内存,而是从操作系统请求内存,因此可以在调用free后将其直接返回给操作系统。


CPython有自己的内存分配器 - pymalloc,它建立在C-runtime-分配器之上。它针对生活在特殊舞台中的小物体进行了优化;与基础 C 运行时分配器相比,创建/释放这些对象时的开销更少。但是,大于 512 字节的对象不使用此竞技场,而是由 C 运行时分配器直接管理。

对于 numpy 的数组,情况甚至更加复杂,因为不同的内存分配器用于元数据(如形状、数据类型和其他标志)和实际数据本身:

  1. 对于元数据PyArray_malloc,CPython的内存分配器(即pymalloc)被使用。
  2. 对于数据本身,使用PyDataMem_NEW,它直接利用底层的 C 运行函数:
NPY_NO_EXPORT void *
PyDataMem_NEW(size_t size)
{
void *result;
result = malloc(size);
...
return result;
}

我不确定,这个设计背后的确切想法是什么:显然人们想从pymalloc的小对象优化中获取,对于数据,这种优化永远不会奏效,但是可以使用PyMem_RawMalloc而不是malloc。也许目标是能够将 numpy 数组包裹在 C 例程分配的内存周围并接管内存的所有权(但这在某些情况下不起作用,请参阅我在本文末尾的评论)。

这解释了您正在观察的行为:对于数据(其大小根据传递的 size-参数而变化),使用PyDataMem_NEW,它绕过了 CPython 的内存分配器,您会看到 C 运行时分配器的原始行为。


人们应该尽量避免混合不同的分配/释放例程PyArray_malloc/PyDataMem_NEW'/mallocandPyArray_free/PyDataMem_FREE/免费":即使它适用于手头的OS + Python版本,它也可能会失败用于其他组合。

例如,在 Windows 上,当使用不同的编译器版本生成扩展时,一个可执行文件可能具有来自不同 C 运行时的不同内存分配器,并且malloc/free可能与不同的 C 内存分配器通信,这可能会导致难以跟踪错误。

最新更新