12

For example if I do this:

cdef np.ndarray[np.int64_t, ndim=1] my_array

Where is my my_array stored? I would think that since I didn't tell cython to store in on the heap it would be stored on the stack, but after doing the following experiment it seems that it is stored on the heap, or somehow efficiently memory managed. How is memory managed with respect to my_array? Maybe I am missing something obvious, but I couldn't find any documentation on it.

import numpy as np
cimport cython
cimport numpy as np

from libc.stdlib cimport malloc, free

def big_sum():
    # freezes up:
    # "a" is created on the stack
    # space on the stack is limited, so it runs out

    cdef int a[10000000]

    for i in range(10000000):
        a[i] = i

    cdef int my_sum
    my_sum = 0
    for i in range(10000000):
        my_sum += a[i]
    return my_sum

def big_sum_malloc():
    # runs fine:
    # "a" is stored on the heap, no problem

    cdef int *a
    a = <int *>malloc(10000000*cython.sizeof(int))

    for i in range(10000000):
        a[i] = i

    cdef int my_sum
    my_sum = 0
    for i in range(10000000):
        my_sum += a[i]

    with nogil:
        free(a) 
    return my_sum    

def big_numpy_array_sum():
    # runs fine:
    # I don't know what is going on here
    # but given that the following code runs fine,
    # it seems that entire array is NOT stored on the stack

    cdef np.ndarray[np.int64_t, ndim=1] my_array
    my_array = np.zeros(10000000, dtype=np.int64)

    for i in range(10000000):
        my_array[i] = i

    cdef int my_sum
    my_sum = 0
    for i in range(10000000):
        my_sum += my_array[i]
    return my_sum
Akavall
  • 82,592
  • 51
  • 207
  • 251
  • 4
    Why don't you give a look at the generated C file? Anyway I believe cython simply calls numpy functions for allocation, which call `PyMalloc` which allocates on the heap. numpy does *not* manage its memory. It simply relies on python allocations/deallocations. – Bakuriu Nov 15 '13 at 16:20
  • 1
    @Bakuriu, Thank You for your comment, it makes sense and helps a lot, but do you know of a source that explains those steps in greater detail? I tried looking at the generated C file, but it is over 6000 lines of code, and I could not make much sense out it. – Akavall Nov 15 '13 at 17:32
  • It's almost certainly heap -- consider that size of array is not known at declaration time, numpy usually works on large arrays and stack is limited. Although stack optimization is technically possible, `ndarray`s can be views, thus data reference can escape current scope. As such, it's way simpler to implement it in heap. Use a MemoryView if possible, or read up http://docs.cython.org/src/tutorial/numpy.html – Dima Tisnek Nov 18 '13 at 10:53
  • link for `MemoryView`s http://docs.cython.org/src/userguide/memoryviews.html – Dima Tisnek Nov 18 '13 at 10:54

1 Answers1

2

Cython is not doing anything magical here. Numpy has a full C-api, and that's what cython is interacting with -- cython is not performing the memory management itself, and memory in the numpy array is handled the same way it is when using a numpy array from python. @Bakuriu is right -- this is definitely on the heap.

Consider this cython code:

cimport numpy as np
def main():
    zeros = np.zeros
    cdef np.ndarray[dtype=np.double_t, ndim=1] array
    array = zeros(10000)

This gets translated to the following C in equivalent main function. I've removed the declarations and error handling code to make it cleaner to read.

PyArrayObject *__pyx_v_array = 0;
PyObject *__pyx_v_zeros = NULL;
PyObject *__pyx_t_1 = NULL;
PyObject *__pyx_t_2 = NULL;

// zeros = np.zeros             # <<<<<<<<<<<<<<
// get the numpy module object
__pyx_t_1 = __Pyx_GetModuleGlobalName(__pyx_n_s__np);
// get the "zeros" function
__pyx_t_2 = __Pyx_PyObject_GetAttrStr(__pyx_t_1, __pyx_n_s__zeros)
__pyx_v_zeros = __pyx_t_2;

// array = zeros(10000)             # <<<<<<<<<<<<<<
// (__pyx_k_tuple_1 is a static global variable containing the literal python tuple
// (10000, ) that was initialized during the __Pyx_InitCachedConstants function)
__pyx_t_2 = PyObject_Call(__pyx_v_zeros, ((PyObject *)__pyx_k_tuple_1), NULL);
__pyx_v_array = ((PyArrayObject *)__pyx_t_2);

If you look up the numpy C api documentation, you'll see that PyArrayObject is the numpy ndarray's C-api struct. The key point here is to see that cython isn't explicitly handling memory allocation at all. The same object orientated design principles apply with the python and numpy C apis, and memory management here is the responsibility of PyArrayObject. The situation no different from the use of a numpy array in python.

Robert T. McGibbon
  • 5,075
  • 3
  • 37
  • 45