python 管理 allocation/freeing 大变量的策略是什么?

What is python's strategy to manage allocation/freeing of large variables?

作为 this question 的后续,在 (C)Python.
中似乎有不同的 allocation/deallocation 小变量和大变量策略 更准确地说,对象大小似乎有一个边界,超过该边界,分配的对象使用的内存可以返回给OS。低于此大小,内存不会还给 OS。

引用 Numpy 释放内存策略的答案:

The exception is that for large single allocations (e.g. if you create a multi-megabyte array), a different mechanism is used. Such large memory allocations can be released back to the OS. So it might specifically be the non-numpy parts of your program that are producing the issues you see.

的确,这两种分配策略很容易展示。例如:

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

=> 没有内存还给 OS

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

x = np.random.uniform(0,1, size=(10**5))

del x
gc.collect()
# We go from 41582.592 KB to 41017.344 KB

=>内存释放到OS

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

所以:

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

当 CPython allocates/deallocates 内存通过 malloc/free 时,它不直接与底层 OS 通信,而是与内存分配器的具体实现通信。就我而言 Linux,它是 the GNU Allocator

GNU 分配器有不同的所谓的区域,内存不会返回到 OS,而是保留下来以便可以重复使用而无需与 OS 通信。但是,如果请求大量内存(无论 "large" 的定义如何),分配器不会使用来自 arenas 的内存,而是从 OS 请求内存,因此可以直接返回到 OS,一旦 free 被调用。


CPython 有自己的内存分配器 - pymalloc,它建立在 C-运行 时间分配器之上。它针对生活在特殊领域的小物体进行了优化;与底层 C-运行 时间分配器相比,creating/freeing 这些对象的开销更少。但是,大于 512 字节的对象不使用此区域,而是由 C-运行time-allocator 直接管理。

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 例程分配的内存周围并接管内存的所有权(但这在某些情况下不起作用,请参阅我在本文末尾的评论 post)。

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


应尽量避免混合使用不同的 allocations/deallocations 例程 PyArray_malloc/PyDataMem_NEW'/mallocandPyArray_free/PyDataMem_FREE/free`:即使它在手头的 OS+Python 版本上工作,它也可能会因其他组合而失败。

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