c возвращение указателя массива malloc в cython

Как эффективно вернуть указатель массива malloc (или указатель на пустой массив) в Cython обратно в python3, эффективно?

Код Cython работает отлично, пока я не возвращаю указатель массива

Мне бы хотелось:

def double complex* randn_zig(int n):
  ...
  r = malloc(n*n*sizeof(double complex))
  ...
  return r

C11 (GCC 11) эквивалент:

double complex* randn_zig(int n){

    r = malloc(n*n*sizeof(double complex))

    return r
}

я пытался<double complex*> randn_zig(int n):

а также randn_zig(<double complex*> r, int n):

и другие перестановки пока безуспешны. Версия кода c и cython в 5 раз быстрее, чем версия Numby/ pylab randn, если я смогу найти способ вернуть указатель на большой двойной комплексный массив размером от 10^6 до 10^10.

4 ответа

Numpy C API

Ваш вопрос похож на этот пост.

Вы можете использовать функцию ниже, чтобы передать указатель C на массив Numpy. Память будет освобождена автоматически при повторном использовании массива Numpy. Если вы хотите освободить указатель, не устанавливайте флаг NPY_OWNDATA.

import numpy as np
cimport numpy as np

cdef pointer_to_numpy_array_complex128(void * ptr, np.npy_intp size):
    '''Convert c pointer to numpy array.
    The memory will be freed as soon as the ndarray is deallocated.
    '''
    cdef extern from "numpy/arrayobject.h":
        void PyArray_ENABLEFLAGS(np.ndarray arr, int flags)
    cdef np.ndarray[np.complex128, ndim=1] arr = \
            np.PyArray_SimpleNewFromData(1, &size, np.NPY_COMPLEX128, ptr)
    PyArray_ENABLEFLAGS(arr, np.NPY_OWNDATA)
    return arr

Для справки:

Cython Typed Memoryviews

Конечно, вы также можете использовать Cython Memoryview.

import numpy as np
cimport numpy as np

cdef np.complex128_t[:,:] view = <np.complex128_t[:n,:n]> c_pointer
numpy_arr = np.asarray(view)

Приведенный выше код переведет указатель C в массив Numpy. Однако это не освободит память автоматически, вы должны освободить память самостоятельно или это приведет к утечке памяти!

Еще один вариант (в дополнение к двум вариантам из верхнего ответа: и просто возврату типизированного представления памяти без обработки памяти) заключается в использовании cython.view.arrayкласс .

Это довольно низкоуровневый класс, который можно использовать для переноса существующей памяти. Он имеет атрибут callback_free_dataгде вы можете установить функцию, которая будет вызываться при уничтожении, чтобы она освобождала память (пример кода здесь скопирован из документации):

      cdef view.array my_array = view.array(..., mode="fortran", allocate_buffer=False)
my_array.data = <char *> my_data_pointer

# define a function that can deallocate the data (if needed)
my_array.callback_free_data = free

Он предоставляет протокол буфера, так что вы можете индексировать его, использовать его с типизированными представлениями памяти или обернуть его массивом Numpy (без копирования) с помощью np.asarray. Последняя функция может быть проще в использовании, чем PyArray_SimpleNewFromData.

Я думаю, что лучший подход состоит в том, чтобы передать указатель существующего массива, созданного в Python, через NumPy в Cython, в противном случае кажется, что вы должны скопировать содержимое массива, созданного malloc в другой массив, как показано в этом игрушечном примере:

import numpy as np
cimport numpy as np

from libc.stdlib cimport malloc, free

def main():
  cdef int i, n=40
  cdef double complex *r
  cdef np.ndarray[np.complex128_t, ndim=1] a
  a = np.zeros(n*n, dtype=np.complex128)
  r = <double complex *>malloc(n*n*sizeof(double complex))
  for i in range(n*n):
      r[i] = 1.
  for i in range(n*n):
      a[i] = r[i]
  free(r)
  return a

Для gcc 5+ с использованием стандарта C-11 ( gcc -std=gnu11 . . .) синтаксис для многомерных массивов malloc и calloc значительно изменился.

Процедура main() для создания двумерного двойного комплексного массива calloc r[n][n] для n = 1024 теперь:

long n = 1024;
complex double (*r)[n] = calloc(n, sizeof *r);

Пример генератора гауссовых случайных чисел randn_box_muller(), использующего указатель на этот массив calloc r[n][n]:

inline static void randn_box_muller(long n, complex double r[][n])
{
    long i, j; 
    register double x, y;

    for(i = 0; i < n; i++){
        for(j = 0; j < n; j++){  
            x = 2.*M_PI*dsfmt_genrand_close_open(&dsfmt);
            y = sqrt(-2.*log(dsfmt_genrand_close_open(&dsfmt)));
            r[i][j] = (cos(x) + I*sin(x))*y;
        }
     }
     return;
}

Этот относительно новый синтаксис распределения вызовов является немного странным. Он хорошо работает для 1, 2 и даже n-мерных массивов calloc и malloc. Надеюсь, это также будет работать в сочетании с Python3. Я надеюсь скоро это проверить.

Другие вопросы по тегам