【问题标题】:How to declare 2D c-arrays dynamically in Cython如何在 Cython 中动态声明二维 c 数组
【发布时间】:2014-09-18 16:12:03
【问题描述】:

我需要使用各种大小的 2D numpy 数组执行大量工作,并且我想将这些计算卸载到 cython 上。这个想法是我的 2D numpy 数组将从 python 传递到 cython,在那里它将被转换为 c 数组或内存视图,并用于级联其他 c 级函数来进行计算。

经过一些分析后,由于一些严重的 python 开销,我排除了在 cython 中使用 numpy 数组。使用内存视图要快得多并且非常易于使用,但我怀疑我可以通过使用 c 数组来获得更多的加速。

不过,这是我的问题 - 如何在 cython 中声明 2D c 数组而不用设定值预定义其尺寸?例如,我可以通过这种方式从 numpy 创建一个 c 数组:

narr = np.array([[1,2,3,4],[5,6,7,8],[9,10,11,12]], dtype=np.dtype("i"))

cdef int c_arr[3][4]:
for i in range(3):
    for j in range(4):
        c_arr[i][j] = narr[i][j]

然后将其传递给函数:

cdef void somefunction(int c_Arr[3][4]):
    ...

但这意味着我有一个固定大小的数组,在我的情况下这将是无用的。所以我尝试了这样的事情:

narr = np.array([[1,2,3,4],[5,6,7,8],[9,10,11,12]], dtype=np.dtype("i"))

cdef int a = np.shape(narr)[0]
cdef int b = np.shape(narr)[1]

cdef int c_arr[a][b]:               # INCORRECT - EXAMPLE ONLY

for i in range(a):
    for j in range(b):
        c_arr[i][j] = narr[i][j]

打算将它传递给这样的函数:

cdef void somefunction(int a, int b, int c_Arr[a][b]):
    ...

但它不起作用并且编译失败并出现错误“不允许在常量表达式中”。我怀疑我不需要以某种方式使用 malloc/free 吗?我查看了这个问题 (How to declare 2D list in Cython),但它没有为我的问题提供答案。

更新:

事实证明,如果确保在 cython 中为内存视图关闭 indexError 检查,那么内存视图可以与 c 数组一样快,这可以通过使用 cython 编译器指令来完成:

# cython: boundscheck=False

感谢@Veedrac 的提示!

【问题讨论】:

  • 另外请注意,每当你有像“2700 倍快”这样的愚蠢数字时,那是因为它已经完全优化了。如果您实际使用输出,收益会小得多。
  • 啊,是的,好点子!同样使用浮点数/双精度数而不是整数也会产生影响......
  • 除了“# cython: boundscheck=False”,您还可以使用“#cython: cython.wraparound=False”寻求额外的加速

标签: c numpy cython dynamic-arrays memoryview


【解决方案1】:

你只需要停止做边界检查:

with cython.boundscheck(False):
    thesum += x_view[i,j]

这使得速度基本上达到了标准。


如果你真的想要一个 C 数组,试试:

import numpy as numpy
from numpy import int32
from numpy cimport int32_t

numpy_array = numpy.array([[]], dtype=int32)

cdef:
    int32_t[:, :] cython_view = numpy_array
    int32_t *c_integers_array = &cython_view[0, 0]
    int32_t[4] *c_2d_array = <int32_t[4] *>c_integers_array

首先你得到一个 Numpy 数组。你用它来获得内存视图。然后你得到一个指向它的数据的指针,你把它转换成所需步幅的指针。

【讨论】:

  • 太棒了,感谢@Veedrac,这确实让我恢复到与 c_arrays 相同的速度!
  • 但是,我仍然想知道如何(如果可以)通过动态指定其大小来生成 2D c 数组?我已经声明了“cdef int carr = malloc(absizeof(int))”,然后使用两个 for 循环来分配单个数组"carr[(i*a)+j] = narr[i][j]" 的值,但它仍然不是一个正确的二维数组,我也不能将它作为二维数组传递给函数......有人吗?
  • 这能回答你的问题吗?
  • 哈!是的,确实如此,再次感谢您!我现在可以看到,内存视图方法比使用 c_arrays 更容易,而且更重要的是 - 速度也一样快!已排序。
【解决方案2】:

因此,在 @Veedrac 的宝贵帮助(非常感谢!)之后,我终于想出了一个脚本,该脚本演示了如何使用内存视图和 c 数组来加速 Cython 中的计算。它们都下降到相似的速度,所以我个人认为使用内存视图要容易得多。

这是一个示例 cython 脚本,它“接受”一个 numpy 数组并将其转换为内存视图或 c 数组,然后通过 c 级函数执行简单的数组求和:

# cython: boundscheck=False

cimport cython
import numpy as np
cimport numpy as np

from numpy import int32
from numpy cimport int32_t


#Generate numpy array:
narr = np.array([[1,2,3,4],[5,6,7,8],[9,10,11,12],[13,14,15,16]], dtype=np.dtype("i"))

cdef int a = np.shape(narr)[0]
cdef int b = np.shape(narr)[1]
cdef int i, j

testsum = np.sum(narr)
print "Test summation: np.sum(narr) =", testsum

#Generate the memory view:
cdef int [:,:] x_view = narr

#Generate the 2D c-array and its pointer:
cdef:
    int32_t[:, :] cython_view = narr
    int32_t *c_integers_array = &cython_view[0, 0]
    int32_t[4] *c_arr = <int32_t[4] *>c_integers_array


def test1():

    speed_test_mview(x_view)  

def test2():

    speed_test_carray(&c_arr[0][0], a, b)


cdef int speed_test_mview(int[:,:] x_view):

    cdef int n, i, j, thesum

    # Define the view:
    for n in range(10000):
        thesum = 0
        for i in range(a):
            for j in range(b):
                thesum += x_view[i, j]        


cdef int speed_test_carray(int32_t *c_Arr, int a, int b):

    cdef int n, i, j, thesum
    for n in range(10000):
        thesum = 0
        for i in range(a):
            for j in range(b):
                thesum += c_Arr[(i*b)+j]

然后使用 ipython shell 计时测试显示相似的速度:

import testlib as t
Test summation: np.sum(narr) = 136

%timeit t.test1()
10000000 loops, best of 3: 46.3 ns per loop

%timeit t.test2()
10000000 loops, best of 3: 46 ns per loop

哦,为了比较 - 在​​这个例子中使用 numpy 数组需要 125 毫秒(未显示)。

【讨论】:

  • 您正在使用&amp;c_arr[0][0]c_arr 转换为一维数组,但您也可以使用c_integers_array,这完全一样。 c_arr 的优点是您可以将其传递给接受int[4] *array 甚至int array[4][4] 的函数。
  • 此外,如果您采取适当的时间安排来防止非边界检查版本完全优化,您会注意到两件事。首先是访问全局变量ab 比访问cdef int a, b; a = x_view.shape[0]; b = x_view.shape[1] 和循环访问这些变量要慢得多。第二个是边界检查有大约 60% 的开销(例如从 1s → 1.6s)。
猜你喜欢
  • 2020-08-01
  • 1970-01-01
  • 2013-12-17
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-12-09
  • 2021-04-07
相关资源
最近更新 更多