Как обрабатывается память для np.ndarray в cython?

Например, если я это сделаю:

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

Где хранится мой my_array? Я бы подумал, что, поскольку я не сказал, что cython хранит в куче, он будет храниться в стеке, но после выполнения следующего эксперимента кажется, что он хранится в куче или как-то эффективно управляется памятью. Как память управляется по отношению к my_array? Может быть, мне не хватает чего-то очевидного, но я не мог найти на нем никакой документации.

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

Ответ 1

Китон не делает здесь ничего магического. У Numpy есть полный C-api, и то, с чем взаимодействует cython с - cython, не выполняет самого управления памятью, а память в массиве numpy обрабатывается так же, как при использовании массива numpy из python. @Bakuriu прав - это определенно в куче.

Рассмотрим этот код cython:

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

Это преобразуется в следующую C в эквивалентной основной функции. Я удалил объявления и код обработки ошибок, чтобы сделать его более чистым для чтения.

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);

Если вы посмотрите на документацию numpy C api, вы увидите, что PyArrayObject - это numpy ndarray C-api struct. Ключевым моментом здесь является увидеть, что cython явно не обрабатывает распределение памяти. Те же принципы, ориентированные на объекты, применимы к python и numpy C apis, а управление памятью здесь принадлежит PyArrayObject. Ситуация не отличается от использования массива numpy в python.