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

什么是python';s管理大变量分配/释放的策略?,python,numpy,cpython,Python,Numpy,Cpython,作为后续研究,在(C)Python中,对于小变量和大变量似乎有不同的分配/解除分配策略。 更准确地说,对象大小中似乎有一个边界,在该边界之上,分配对象使用的内存可以返回给操作系统。低于此大小时,内存不会返回给操作系统 引用Numpy释放内存策略中的答案: 例外情况是,对于大型单次分配(例如,如果您创建了一个多兆字节数组),则使用不同的机制。这样大的内存分配可以释放回操作系统。因此,可能是程序中的非numpy部分产生了您看到的问题 事实上,这两种分配策略很容易表现出来。例如: 第一个策略:不向操

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

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

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

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

  • 第一个策略:不向操作系统返回内存
将numpy导入为np
导入psutil
导入gc
#分配数组
x=np.随机.均匀(0,1,大小=(10**4))
#gc
德尔克斯
gc.collect()
#我们从41295.872kb到41295.872kb
#使用psutil.Process().memory_info().rss/10**3;虚拟机也有同样的行为
=>没有返回操作系统的内存

  • 第二种策略:释放的内存返回操作系统
当进行相同的实验,但使用更大的阵列时:

x=np.random.uniform(0,1,大小=(10**5))
德尔克斯
gc.collect()
#我们从41582.592KB到41017.344KB
=>内存已释放到操作系统

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

因此:

  • 这种行为有记录吗?(分配策略改变的确切界限是什么?)
  • 这些策略的内部结构是什么(不仅仅是假设使用
    mmap
    /
    munmap
    将内存释放回操作系统)
  • 这是由Python运行时100%完成的,还是Numpy有一种特定的处理方法?(提到在内存分配器之间切换的
    NPY\u USE\u PYMEM

您观察到的不是CPython的策略,而是CPython版本使用的C-runtime附带的内存分配器的策略

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

GNU分配器有不同的所谓Arena,其中内存不会返回到操作系统,而是保留下来,这样就可以重用,而无需与操作系统通信。但是,如果请求了大量内存(无论“大”的定义如何),分配器不会使用arenas提供的内存,而是从操作系统请求内存,因此,一旦调用了
free
,就可以直接将内存返回给操作系统


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

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

  • 对于元数据,使用CPython的内存分配器(即pymalloc)
  • 对于数据本身,使用,它直接使用底层C-runtimme-functionality:
  • NPY\u否\u导出无效*
    PyDataMem_新(大小)
    {
    无效*结果;
    结果=malloc(大小);
    ...
    返回结果;
    }
    
    我不确定,这种设计背后的确切想法是什么:很明显,人们希望从pymalloc的小对象优化中获益,对于数据,这种优化永远不会起作用,但是人们可以使用它来代替
    malloc
    。也许我们的目标是能够围绕C例程分配的内存包装numpy数组,并接管内存的所有权(但这在某些情况下不起作用,请参阅本文末尾我的评论)

    这解释了您观察到的行为:对于数据(其大小根据中传递的size参数而变化)
    PyDataMem\u使用NEW
    ,它绕过了CPython的内存分配器,您可以看到C-runtime的分配器的原始行为


    我们应该尽量避免混合使用不同的分配/解除分配例程
    PyDataMem\u malloc
    /
    PyDataMem\u NEW'/
    malloc
    PyArray\u free
    /
    PyDataMem\u free
    //code>free`:即使它在手头的OS+Python版本下工作,它也可能因其他组合而失败


    例如,在Windows上,当使用不同的编译器版本构建扩展时,一个可执行文件可能具有来自不同C运行时的不同内存分配器,
    malloc/free
    可能与不同的C内存分配器通信,这可能导致难以跟踪错误;这是一个实现细节。请理解
    gc.collect()
    在这里完全是一个骗局。
    gc
    模块仅适用于CPython中的循环垃圾收集机制。您还没有创建任何引用周期,因此它不需要做任何事情。内存是否返回到操作系统取决于运行时附带的内存管理器(在我的Linux上是glibc)。这些管理器通常不会费心将小块内存返回到操作系统,并在以后将该内存重新用于类似大小的分配。pymalloc可以依赖不同的分配函数,
    mmap
    /
    munmap
    malloc
    /
    free
    ():而malloc是一个C运行时分配器(这本身可以称为