On 09/12/14 08:40, Robert McGibbon wrote:
> Is it possible to allocate a typed memoryview without holding the GIL?
I don't think so. You can slice and index a typed memoryview without
holding the GIL, though. As I understand it (correct me if I am wrong),
casting a pointer to a typed memoryview will wrap the pointer with an
instance of cython.view.array, which is a Python object (and thus its
refcounting will require the GIL).
> from libc.stdlib cimport malloc, free
Be aware that libc.stdlib functions also require the GIL.
> cdef double* p
> cdef double[::1] X
> cdef size_t N
>
> with nogil:
> p = <double* >malloc(N*sizeof(double))
> X = <double[:N]> p
> [...]
> free(p)
Note that you can temporarily grab the GIL back. malloc and free also
needs protection against Python exceptions when coding in Cython (unlike
C but similar to C++). Thus I would end with something alike this:
```
# file heapmem.pyx
cdef extern from "Python.h":
ctypedef long Py_intptr_t
void *PyMem_Malloc(size_t)
void PyMem_Free(void*)
cdef class Heapmem:
"""
A class that wraps PyMem_Malloc and PyMem_Free for
safe use with Cython.
"""
cdef:
void *_pointer
Py_ssize_t size
def __cinit__(Heapmem self, Py_ssize_t n):
self._pointer = NULL
self._size = n
def __init__(Heapmem self, Py_ssize_t n):
self.allocate()
def allocate(Heapmem self):
if self._pointer != NULL:
raise RuntimeError("Memory already allocated")
else:
self._pointer = PyMem_Malloc(self._size)
if (self._pointer == NULL):
raise MemoryError()
def __dealloc__(Heapmem self):
if self._pointer != NULL:
PyMem_Free(self._pointer)
self._pointer = NULL
property pointer:
def __get__(Heapmem self):
if self._pointer != NULL:
return <Py_intptr_t> self._pointer
else:
raise RuntimeError("Memory not allocated")
property doublearray:
def __get__(Heapmem self):
cdef Py_ssize_t n = self._size//sizeof(double)
cdef double *dp = <double*> self._pointer
cdef double[::1] out
if self._pointer != NULL:
out = <double[:n:1]> dp
return out
else:
raise RuntimeError("Memory not allocated")
def __enter__(self):
if self._pointer != NULL:
raise RuntimeError("Memory not allocated")
def __exit__(Heapmem self, type, value, traceback):
if self._pointer != NULL:
PyMem_Free(self._pointer)
self._pointer = NULL
```
And then:
```
from heapmem import Heapmem
cdef double[::1] X
cdef Py_ssize_t N
# do stuff with the GIL
[...]
# explicitly release the GIL
with nogil:
# do stuff without the GIL
[...]
# explicitly grab the GIL back
with gil:
# do stuff with the GIL
[...]
# allocate memory
with Heapmem(N*sizeof(double)) as p:
# wrap pointer with cython.view.array and
# assign to typed memoryview
X = p.doublearray
# explicitly release the GIL
with nogil:
# do stuff without the GIL
[...]
# implicitly get the GIL back here
# do stuff with the GIL
[...]
# memory in is ALWAYS freed here, not postponed
# to when is garbage collected
# do stuff with the GIL
[...]
# implicitly release the GIL here
# do stuff without the GIL
[...]
# implicitly get the GIL back
# do stuff with the GIL
[...]
```
This allows us to finely control when we grab the GIL, and it is also
safe against memory leaks due to Python exceptions. If we just drop
malloc and free into Cython we have to be very careful to use
try-finally to ensure that a Python exception do not unpair them. A
context manager is much easier to work with.
Another thing is that wrapping malloc and free in a context manager
allows us to accurately control when memory is allocated and
deallocated. In particular, a reference leak will not produce a memory leak.
Sturla