gpt4 book ai didi

python - Cython容器不释放内存吗?

转载 作者:太空宇宙 更新时间:2023-11-04 01:57:19 32 4
gpt4 key购买 nike

当我运行下面的代码时,我希望一旦 foo() 被执行,它所使用的内存(主要是为了创建 m)就会被释放。然而,事实并非如此。要释放此内存,我需要重新启动 IPython 控制台。

%%cython
# distutils: language = c++

import numpy as np
from libcpp.map cimport map as cpp_map

cdef foo():
cdef:
cpp_map[int,int] m
int i
for i in range(50000000):
m[i] = i

foo()

如果有人能告诉我为什么会这样,以及如何在不重新启动 shell 的情况下释放此内存,那就太好了。提前致谢。

最佳答案

您看到的效果或多或少是内存分配器(可能是 glibc 的默认分配器)的实现细节。 glibc 的内存分配器工作方式如下:

  • arenas 满足了对小内存大小的请求,它会根据需要增长/数量增长。
  • 对大内存的请求直接从 OS 获取,但在它们被释放后也会直接返回给 OS。

可以使用 mallopt 来调整何时释放这些竞技场的内存,但通常使用内部启发式来决定何时/是否应将内存返回给操作系统 - 我最承认这对我来说是一种黑魔法。

std::map 的问题(std::unordered_map 的情况类似)是,它不包含大块内存将立即返回给操作系统,但是有很多小节点(映射由 libstdc++ 实现为 Red-Black-Tree)- 因此它们都来自这些领域,启发式决定不将其返回给操作系统。

因为我们正在使用 glibc 的分配器,所以可以使用非标准函数 malloc_trim手动释放内存:

%%cython

cdef extern from "malloc.h" nogil:
int malloc_trim(size_t pad)

def return_memory_to_OS():
malloc_trim(0)

现在只需在每次使用 foo 后调用 return_memory_to_OS()


上面的解决方案是快速和肮脏的,但不可移植。你想要的是一个自定义分配器,一旦不再使用它就会将内存释放回操作系统。这是很多工作 - 但幸运的是我们手头已经有了这样的分配器:CPython 的 pymalloc - 从 Python2.5 开始,它将内存返回给操作系统(即使它意味着 sometimes trouble)。然而,我们也应该指出 pymalloc 的一个很大的缺陷——它不是线程安全的,所以它可以仅用于带有 gil 的代码!

使用 pymalloc-allocator 不仅有将内存返回给操作系统的优点,而且因为 pymalloc 是 8 字节对齐的,而 glibc 的分配器是 32 字节对齐的,因此产生的内存消耗会更小(节点 map[int,int ] 是 40 字节,这将花费 only 40.5 bytes with pymalloc(连同开销),而 glibc 将需要不少于 64 字节)。

我的自定义分配器实现遵循 Nicolai M. Josuttis' example并只实现真正需要的功能:

%%cython -c=-std=c++11 --cplus

cdef extern from *:
"""
#include <cstddef> // std::size_t
#include <Python.h> // pymalloc

template <class T>
class pymalloc_allocator {
public:
// type definitions
typedef T value_type;
typedef T* pointer;
typedef std::size_t size_type;

template <class U>
pymalloc_allocator(const pymalloc_allocator<U>&) throw(){};
pymalloc_allocator() throw() = default;
pymalloc_allocator(const pymalloc_allocator&) throw() = default;
~pymalloc_allocator() throw() = default;

// rebind allocator to type U
template <class U>
struct rebind {
typedef pymalloc_allocator<U> other;
};

pointer allocate (size_type num, const void* = 0) {
pointer ret = static_cast<pointer>(PyMem_Malloc(num*sizeof(value_type)));
return ret;
}

void deallocate (pointer p, size_type num) {
PyMem_Free(p);
}

// missing: destroy, construct, max_size, address
// -
};

// missing:
// bool operator== , bool operator!=

#include <utility>
typedef pymalloc_allocator<std::pair<int, int>> PairIntIntAlloc;

//further helper (not in functional.pxd):
#include <functional>
typedef std::less<int> Less;
"""
cdef cppclass PairIntIntAlloc:
pass
cdef cppclass Less:
pass


from libcpp.map cimport map as cpp_map

def foo():
cdef:
cpp_map[int,int, Less, PairIntIntAlloc] m
int i
for i in range(50000000):
m[i] = i

现在,一旦 foo 完成,大部分已用内存将返回给操作系统 - 在任何操作系统和内存分配器上!


如果内存消耗是个问题,可以切换到需要较少内存的unorder_map。然而,目前 unordered_map.pxd 不提供对所有模板参数的访问,因此必须手动包装它:

%%cython -c=-std=c++11 --cplus

cdef extern from *:
"""
....

//further helper (not in functional.pxd):
#include <functional>
...
typedef std::hash<int> Hash;
typedef std::equal_to<int> Equal_to;
"""
...
cdef cppclass Hash:
pass
cdef cppclass Equal_to:
pass

cdef extern from "<unordered_map>" namespace "std" nogil:
cdef cppclass unordered_map[T, U, HASH=*,RPED=*, ALLOC=* ]:
U& operator[](T&)

N = 5*10**8

def foo_unordered_pymalloc():
cdef:
unordered_map[int, int, Hash, Equal_to, PairIntIntAlloc] m
int i
for i in range(N):
m[i] = i

这里有一些基准,显然不完整,但可能很好地显示了方向(但对于 N=3e7 而不是 N=5e8):

                                   Time           PeakMemory

map_default 40.1s 1416Mb
map_default+return_memory 41.8s
map_pymalloc 12.8s 1200Mb

unordered_default 9.8s 1190Mb
unordered_default+return_memory 10.9s
unordered_pymalloc 5.5s 730Mb

计时是通过 %timeit 魔法完成的,峰值内存使用是通过 via/usr/bin/time -fpeak_used_memory:%M python script_xxx.py 完成的。

我有点惊讶,pymalloc 的性能远远优于 glibc-allocator,而且内存分配似乎是普通映射的瓶颈!也许这就是 glibc 为支持多线程所必须付出的代价。

unordered_map 更快并且可能需要更少的内存(好吧,因为重新散列最后一部分可能是错误的)。

关于python - Cython容器不释放内存吗?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/56540257/

32 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com