cymem: Cython内存辅助工具
cymem为Cython提供了两个小型内存管理辅助工具。它们可以轻松地将内存与Python对象的生命周期绑定,使得当对象被垃圾回收时,内存也会被释放。
概述
最有用的是cymem.Pool
,它作为calloc函数的一个薄包装器:
from cymem.cymem cimport Pool
cdef Pool mem = Pool()
data1 = <int*>mem.alloc(10, sizeof(int))
data2 = <float*>mem.alloc(12, sizeof(float))
Pool
对象在内部保存内存地址,并在对象被垃圾回收时释放它们。通常,你会将Pool
附加到某个cdef'd类上。这对于具有复杂初始化函数的深度嵌套结构特别方便。只需将Pool
对象传入初始化器,你就不必担心释放结构体了 —— 所有对Pool.alloc
的调用都会在Pool
过期时自动释放。
安装
通过pip安装,并需要Cython。在安装之前,请确保你的pip
、setuptools
和wheel
是最新的。
pip install -U pip setuptools wheel
pip install cymem
使用案例:结构体数组
假设我们需要一系列稀疏矩阵。我们需要快速访问,而Python列表的性能不够好。因此,我们想要一个C数组或C++向量,这意味着稀疏矩阵需要是C级别的结构体 —— 它不能是Python类。我们可以在Cython中轻松地编写这个:
"""不使用Cymem的示例
要使用结构体数组,我们必须在释放时仔细遍历数据结构。
"""
from libc.stdlib cimport calloc, free
cdef struct SparseRow:
size_t length
size_t* indices
double* values
cdef struct SparseMatrix:
size_t length
SparseRow* rows
cdef class MatrixArray:
cdef size_t length
cdef SparseMatrix** matrices
def __cinit__(self, list py_matrices):
self.length = 0
self.matrices = NULL
def __init__(self, list py_matrices):
self.length = len(py_matrices)
self.matrices = <SparseMatrix**>calloc(len(py_matrices), sizeof(SparseMatrix*))
for i, py_matrix in enumerate(py_matrices):
self.matrices[i] = sparse_matrix_init(py_matrix)
def __dealloc__(self):
for i in range(self.length):
sparse_matrix_free(self.matrices[i])
free(self.matrices)
cdef SparseMatrix* sparse_matrix_init(list py_matrix) except NULL:
sm = <SparseMatrix*>calloc(1, sizeof(SparseMatrix))
sm.length = len(py_matrix)
sm.rows = <SparseRow*>calloc(sm.length, sizeof(SparseRow))
cdef size_t i, j
cdef dict py_row
cdef size_t idx
cdef double value
for i, py_row in enumerate(py_matrix):
sm.rows[i].length = len(py_row)
sm.rows[i].indices = <size_t*>calloc(sm.rows[i].length, sizeof(size_t))
sm.rows[i].values = <double*>calloc(sm.rows[i].length, sizeof(double))
for j, (idx, value) in enumerate(py_row.items()):
sm.rows[i].indices[j] = idx
sm.rows[i].values[j] = value
return sm
cdef void* sparse_matrix_free(SparseMatrix* sm) except *:
cdef size_t i
for i in range(sm.length):
free(sm.rows[i].indices)
free(sm.rows[i].values)
free(sm.rows)
free(sm)
我们将数据结构包装在一个Python引用计数类中,尽可能低级,以满足我们的性能需求。这允许我们在Cython的__cinit__
和__dealloc__
特殊方法中分配和释放内存。
然而,在编写__dealloc__
和sparse_matrix_free
函数时很容易出错,导致内存泄漏。cymem可以让你完全避免编写这些析构函数。相反,你可以这样写:
"""使用Cymem的示例
内存分配隐藏在Pool类后面,它记住了它分配的地址。当Pool对象被垃圾回收时,
它分配的所有地址都会被释放。
我们不需要编写MatrixArray.__dealloc__或sparse_matrix_free,
从而消除了一类常见的错误。
"""
from cymem.cymem cimport Pool
cdef struct SparseRow:
size_t length
size_t* indices
double* values
cdef struct SparseMatrix:
size_t length
SparseRow* rows
cdef class MatrixArray:
cdef size_t length
cdef SparseMatrix** matrices
cdef Pool mem
def __cinit__(self, list py_matrices):
self.mem = None
self.length = 0
self.matrices = NULL
def __init__(self, list py_matrices):
self.mem = Pool()
self.length = len(py_matrices)
self.matrices = <SparseMatrix**>self.mem.alloc(self.length, sizeof(SparseMatrix*))
for i, py_matrix in enumerate(py_matrices):
self.matrices[i] = sparse_matrix_init(self.mem, py_matrix)
cdef SparseMatrix* sparse_matrix_init_cymem(Pool mem, list py_matrix) except NULL:
sm = <SparseMatrix*>mem.alloc(1, sizeof(SparseMatrix))
sm.length = len(py_matrix)
sm.rows = <SparseRow*>mem.alloc(sm.length, sizeof(SparseRow))
cdef size_t i, j
cdef dict py_row
cdef size_t idx
cdef double value
for i, py_row in enumerate(py_matrix):
sm.rows[i].length = len(py_row)
sm.rows[i].indices = <size_t*>mem.alloc(sm.rows[i].length, sizeof(size_t))
sm.rows[i].values = <double*>mem.alloc(sm.rows[i].length, sizeof(double))
for j, (idx, value) in enumerate(py_row.items()):
sm.rows[i].indices[j] = idx
sm.rows[i].values[j] = value
return sm
Pool
类所做的就是记住它分配的地址。当MatrixArray
对象被垃圾回收时,Pool
对象也会被垃圾回收,这会触发对Pool.__dealloc__
的调用。然后Pool
释放它的所有地址。这使你不必回溯嵌套的数据结构来释放它们,从而消除了一类常见的错误。
自定义分配器
有时外部C库使用私有函数来分配和释放对象,但我们仍然希望使用Pool
的惰性特性。
from cymem.cymem cimport Pool, WrapMalloc, WrapFree
cdef Pool mem = Pool(WrapMalloc(priv_malloc), WrapFree(priv_free))