From 3a0ca192ed497f81c7bee60f13f635c9ac265e72 Mon Sep 17 00:00:00 2001 From: 0dminnimda <0dminnimda@gmail.com> Date: Sun, 8 Aug 2021 11:30:07 +0300 Subject: docs: Pythonise documentation on Memory Allocation (memory_allocation.rst) (GH-4316) --- docs/examples/tutorial/memory_allocation/malloc.py | 24 +++++++++++++ .../examples/tutorial/memory_allocation/malloc.pyx | 3 +- .../tutorial/memory_allocation/some_memory.py | 27 +++++++++++++++ .../tutorial/memory_allocation/some_memory.pyx | 8 +++-- docs/src/tutorial/memory_allocation.rst | 40 ++++++++++++++++++---- 5 files changed, 92 insertions(+), 10 deletions(-) create mode 100644 docs/examples/tutorial/memory_allocation/malloc.py create mode 100644 docs/examples/tutorial/memory_allocation/some_memory.py diff --git a/docs/examples/tutorial/memory_allocation/malloc.py b/docs/examples/tutorial/memory_allocation/malloc.py new file mode 100644 index 000000000..fb7e82236 --- /dev/null +++ b/docs/examples/tutorial/memory_allocation/malloc.py @@ -0,0 +1,24 @@ +import random +from cython.cimports.libc.stdlib import malloc, free + +def random_noise(number: cython.int = 1): + i: cython.int + # allocate number * sizeof(double) bytes of memory + my_array: cython.p_double = cython.cast(cython.p_double, malloc( + number * cython.sizeof(cython.double))) + if not my_array: + raise MemoryError() + + try: + ran = random.normalvariate + for i in range(number): + my_array[i] = ran(0, 1) + + # ... let's just assume we do some more heavy C calculations here to make up + # for the work that it takes to pack the C double values into Python float + # objects below, right after throwing away the existing objects above. + + return [x for x in my_array[:number]] + finally: + # return the previously allocated memory to the system + free(my_array) diff --git a/docs/examples/tutorial/memory_allocation/malloc.pyx b/docs/examples/tutorial/memory_allocation/malloc.pyx index c185187d4..6aa583aab 100644 --- a/docs/examples/tutorial/memory_allocation/malloc.pyx +++ b/docs/examples/tutorial/memory_allocation/malloc.pyx @@ -4,7 +4,8 @@ from libc.stdlib cimport malloc, free def random_noise(int number=1): cdef int i # allocate number * sizeof(double) bytes of memory - cdef double *my_array = malloc(number * sizeof(double)) + cdef double *my_array = malloc( + number * sizeof(double)) if not my_array: raise MemoryError() diff --git a/docs/examples/tutorial/memory_allocation/some_memory.py b/docs/examples/tutorial/memory_allocation/some_memory.py new file mode 100644 index 000000000..31ad63a6e --- /dev/null +++ b/docs/examples/tutorial/memory_allocation/some_memory.py @@ -0,0 +1,27 @@ +from cython.cimports.cpython.mem import PyMem_Malloc, PyMem_Realloc, PyMem_Free + +@cython.cclass +class SomeMemory: + data: cython.p_double + + def __cinit__(self, number: cython.size_t): + # allocate some memory (uninitialised, may contain arbitrary data) + self.data = cython.cast(cython.p_double, PyMem_Malloc( + number * cython.sizeof(cython.double))) + if not self.data: + raise MemoryError() + + def resize(self, new_number: cython.size_t): + # Allocates new_number * sizeof(double) bytes, + # preserving the current content and making a best-effort to + # re-use the original data location. + mem = cython.cast(cython.p_double, PyMem_Realloc( + self.data, new_number * cython.sizeof(cython.double))) + if not mem: + raise MemoryError() + # Only overwrite the pointer if the memory was really reallocated. + # On error (mem is NULL), the originally memory has not been freed. + self.data = mem + + def __dealloc__(self): + PyMem_Free(self.data) # no-op if self.data is NULL diff --git a/docs/examples/tutorial/memory_allocation/some_memory.pyx b/docs/examples/tutorial/memory_allocation/some_memory.pyx index fb272a88d..e6bb63b77 100644 --- a/docs/examples/tutorial/memory_allocation/some_memory.pyx +++ b/docs/examples/tutorial/memory_allocation/some_memory.pyx @@ -1,12 +1,13 @@ from cpython.mem cimport PyMem_Malloc, PyMem_Realloc, PyMem_Free -cdef class SomeMemory: +cdef class SomeMemory: cdef double* data def __cinit__(self, size_t number): # allocate some memory (uninitialised, may contain arbitrary data) - self.data = PyMem_Malloc(number * sizeof(double)) + self.data = PyMem_Malloc( + number * sizeof(double)) if not self.data: raise MemoryError() @@ -14,7 +15,8 @@ cdef class SomeMemory: # Allocates new_number * sizeof(double) bytes, # preserving the current content and making a best-effort to # re-use the original data location. - mem = PyMem_Realloc(self.data, new_number * sizeof(double)) + mem = PyMem_Realloc( + self.data, new_number * sizeof(double)) if not mem: raise MemoryError() # Only overwrite the pointer if the memory was really reallocated. diff --git a/docs/src/tutorial/memory_allocation.rst b/docs/src/tutorial/memory_allocation.rst index a6dc0ae1b..fa79310de 100644 --- a/docs/src/tutorial/memory_allocation.rst +++ b/docs/src/tutorial/memory_allocation.rst @@ -4,6 +4,9 @@ Memory Allocation ***************** +.. include:: + ../two-syntax-variants-used + Dynamic memory allocation is mostly a non-issue in Python. Everything is an object, and the reference counting system and garbage collector automatically return memory to the system when it is no longer being used. @@ -19,7 +22,7 @@ In some situations, however, these objects can still incur an unacceptable amount of overhead, which can then makes a case for doing manual memory management in C. -Simple C values and structs (such as a local variable ``cdef double x``) are +Simple C values and structs (such as a local variable ``cdef double x`` / ``x: cython.double``) are usually :term:`allocated on the stack` and passed by value, but for larger and more complicated objects (e.g. a dynamically-sized list of doubles), the memory must be :term:`manually requested and released`. C provides the functions :c:func:`malloc`, @@ -34,8 +37,15 @@ in cython from ``clibc.stdlib``. Their signatures are: A very simple example of malloc usage is the following: -.. literalinclude:: ../../examples/tutorial/memory_allocation/malloc.pyx - :linenos: + +.. tabs:: + .. group-tab:: Pure Python + + .. literalinclude:: ../../examples/tutorial/memory_allocation/malloc.py + + .. group-tab:: Cython + + .. literalinclude:: ../../examples/tutorial/memory_allocation/malloc.pyx Note that the C-API functions for allocating memory on the Python heap are generally preferred over the low-level C functions above as the @@ -45,9 +55,20 @@ smaller memory blocks, which speeds up their allocation by avoiding costly operating system calls. The C-API functions can be found in the ``cpython.mem`` standard -declarations file:: +declarations file: + +.. tabs:: + .. group-tab:: Pure Python + + .. code-block:: python - from cpython.mem cimport PyMem_Malloc, PyMem_Realloc, PyMem_Free + from cython.cimports.cpython.mem import PyMem_Malloc, PyMem_Realloc, PyMem_Free + + .. group-tab:: Cython + + .. code-block:: cython + + from cpython.mem cimport PyMem_Malloc, PyMem_Realloc, PyMem_Free Their interface and usage is identical to that of the corresponding low-level C functions. @@ -64,4 +85,11 @@ If a chunk of memory needs a larger lifetime than can be managed by a to a Python object to leverage the Python runtime's memory management, e.g.: -.. literalinclude:: ../../examples/tutorial/memory_allocation/some_memory.pyx +.. tabs:: + .. group-tab:: Pure Python + + .. literalinclude:: ../../examples/tutorial/memory_allocation/some_memory.py + + .. group-tab:: Cython + + .. literalinclude:: ../../examples/tutorial/memory_allocation/some_memory.pyx -- cgit v1.2.1