Package: cymem
cymem: A Cython Memory Helpercymem provides two small memory-management helpers for Cython. They make it easy
to tie memory to a Python object's life-cycle, so that the memory is freed when
the object is garbage collected.OverviewThe most useful iscymem.Pool, which acts as a thin wrapper around the calloc
function:fromcymem.cymemcimportPoolcdefPoolmem=Pool()data1=<int*>mem.alloc(10,sizeof(int))data2=<float*>mem.alloc(12,sizeof(float))ThePoolobject saves the memory addresses internally, and frees them when the
object is garbage collected. Typically you'll attach thePoolto some cdef'd
class. This is particularly handy for deeply nested structs, which have
complicated initialization functions. Just pass thePoolobject into the
initializer, and you don't have to worry about freeing your struct at all — all
of the calls toPool.allocwill be automatically freed when thePoolexpires.InstallationInstallation is viapip, and requiresCython. Before installing, make sure that yourpip,setuptoolsandwheelare up to date.pipinstall-Upipsetuptoolswheel
pipinstallcymemExample Use Case: An array of structsLet's say we want a sequence of sparse matrices. We need fast access, and a
Python list isn't performing well enough. So, we want a C-array or C++ vector,
which means we need the sparse matrix to be a C-level struct — it can't be a
Python class. We can write this easily enough in Cython:"""Example without CymemTo use an array of structs, we must carefully walk the data structure whenwe deallocate it."""fromlibc.stdlibcimportcalloc,freecdefstructSparseRow:size_tlengthsize_t*indicesdouble*valuescdefstructSparseMatrix:size_tlengthSparseRow*rowscdefclassMatrixArray:cdefsize_tlengthcdefSparseMatrix**matricesdef__cinit__(self,listpy_matrices):self.length=0self.matrices=NULLdef__init__(self,listpy_matrices):self.length=len(py_matrices)self.matrices=<SparseMatrix**>calloc(len(py_matrices),sizeof(SparseMatrix*))fori,py_matrixinenumerate(py_matrices):self.matrices[i]=sparse_matrix_init(py_matrix)def__dealloc__(self):foriinrange(self.length):sparse_matrix_free(self.matrices[i])free(self.matrices)cdefSparseMatrix*sparse_matrix_init(listpy_matrix)exceptNULL:sm=<SparseMatrix*>calloc(1,sizeof(SparseMatrix))sm.length=len(py_matrix)sm.rows=<SparseRow*>calloc(sm.length,sizeof(SparseRow))cdefsize_ti,jcdefdictpy_rowcdefsize_tidxcdefdoublevaluefori,py_rowinenumerate(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))forj,(idx,value)inenumerate(py_row.items()):sm.rows[i].indices[j]=idxsm.rows[i].values[j]=valuereturnsmcdefvoid*sparse_matrix_free(SparseMatrix*sm)except*:cdefsize_tiforiinrange(sm.length):free(sm.rows[i].indices)free(sm.rows[i].values)free(sm.rows)free(sm)We wrap the data structure in a Python ref-counted class at as low a level as we
can, given our performance constraints. This allows us to allocate and free the
memory in the__cinit__and__dealloc__Cython special methods.However, it's very easy to make mistakes when writing the__dealloc__andsparse_matrix_freefunctions, leading to memory leaks. cymem prevents you from
writing these deallocators at all. Instead, you write as follows:"""Example with Cymem.Memory allocation is hidden behind the Pool class, which remembers theaddresses it gives out.  When the Pool object is garbage collected, all ofits addresses are freed.We don't need to write MatrixArray.__dealloc__ or sparse_matrix_free,eliminating a common class of bugs."""fromcymem.cymemcimportPoolcdefstructSparseRow:size_tlengthsize_t*indicesdouble*valuescdefstructSparseMatrix:size_tlengthSparseRow*rowscdefclassMatrixArray:cdefsize_tlengthcdefSparseMatrix**matricescdefPoolmemdef__cinit__(self,listpy_matrices):self.mem=Noneself.length=0self.matrices=NULLdef__init__(self,listpy_matrices):self.mem=Pool()self.length=len(py_matrices)self.matrices=<SparseMatrix**>self.mem.alloc(self.length,sizeof(SparseMatrix*))fori,py_matrixinenumerate(py_matrices):self.matrices[i]=sparse_matrix_init(self.mem,py_matrix)cdefSparseMatrix*sparse_matrix_init_cymem(Poolmem,listpy_matrix)exceptNULL:sm=<SparseMatrix*>mem.alloc(1,sizeof(SparseMatrix))sm.length=len(py_matrix)sm.rows=<SparseRow*>mem.alloc(sm.length,sizeof(SparseRow))cdefsize_ti,jcdefdictpy_rowcdefsize_tidxcdefdoublevaluefori,py_rowinenumerate(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))forj,(idx,value)inenumerate(py_row.items()):sm.rows[i].indices[j]=idxsm.rows[i].values[j]=valuereturnsmAll that thePoolclass does is remember the addresses it gives out. When theMatrixArrayobject is garbage-collected, thePoolobject will also be
garbage collected, which triggers a call toPool.__dealloc__. ThePoolthen
frees all of its addresses. This saves you from walking back over your nested
data structures to free them, eliminating a common class of errors.Custom AllocatorsSometimes external C libraries use private functions to allocate and free
objects, but we'd still like the laziness of thePool.fromcymem.cymemcimportPool,WrapMalloc,WrapFreecdefPoolmem=Pool(WrapMalloc(priv_malloc),WrapFree(priv_free))