在Cython中使用malloc分配数组指针并返回

8

如何在Cython中高效地将malloc数组指针(或numpy数组指针)返回给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),但都没有成功。如果我能找到一种方法来返回一个指向大型 10^6 到 10^10 复数数组的指针,那么 C 和 Cython 的代码版本将比 Numpy/PyLab randn 版本快 5 倍。请注意,在翻译过程中需要保留 HTML 标记。

你能提供更多的代码,以便我们调用这个函数吗?你遇到了哪些错误? - Ashalynd
Python中没有指针的等价物,因此您实际想要返回的不是指针,而是对已分配内存块的某个引用(即,一些具有指向该内存块的指针的Python对象)。您选择哪种Python对象将取决于您打算如何处理指针/内存块。 - Bi Rico
4个回答

9

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类型化内存视图

当然,您也可以使用Cython内存视图

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数组。 但是,这 不会 自动释放内存,您必须自行释放内存,否则会导致内存泄漏!


Syrtis 感谢,我马上尝试。我现在真的需要解决这个问题,因为有个项目要用到。请看下面的评论。 - W9DKI

1
除了前面提到的两个选项(PyArray_SimpleNewFromData和直接返回类型化的内存视图而不处理内存),还有另一种选择,即使用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

它公开了缓冲区协议,使您可以对其进行索引,与类型化内存视图一起使用,或使用np.asarray将其包装为Numpy数组(无需复制)。后者可能比PyArray_SimpleNewFromData更易于使用。


0

我认为最好的方法是将在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

我一直忙于其他项目,现在回来解决这个问题。谢谢。无论如何,Python 3.4和numpy似乎包括Meresenne素数生成器,我假设是dsfmt(),在Ubuntu 15.10中默认安装。我没有查看Python源代码ppython3.4,但在3.2中没有找到它。我不确定它们是否具有Ziggurat或Leva算法用于randn()。我需要查看。如果需要,我将实现其中一个。73 - W9DKI

-1

对于使用C-11标准的gcc 5+(gcc -std = gnu11 ...),多维malloc和calloc数组的语法已经发生了重大变化。

现在创建一个2-D,双精度,复杂的calloc数组r [n] [n](其中n = 1024)的main()过程如下:

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

一个使用指向calloc数组r[n][n]的高斯随机数生成器randn_box_muller()的示例是:
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;
}

这种相对较新的calloc分配语法有点奇怪。它适用于1、2甚至n维的calloc和malloc数组。希望这也能与Python3配合使用。我希望很快就能测试这个。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接