【问题标题】:Cython: declare list-like function parameterCython:声明类似列表的函数参数
【发布时间】:2018-07-10 15:15:32
【问题描述】:

我正在尝试创建一个简单的 cython 模块并遇到以下问题。我想创建一个类似的函数:

cdef float calc(float[:] a1, float[:] a2):
    cdef float res = 0
    cdef int l = len(a2)
    cdef float item_a2
    cdef float item_a1

    for idx in range(l):
        if a2[idx] > 0:
            item_a2 = a2[idx]
            item_a1 = a1[idx]
            res += item_a2 * item_a1

    return res

函数执行时,a1 和 a2 参数是 python 列表。因此我得到错误:

TypeError: 需要一个类似字节的对象,而不是“列表”

我只需要进行这样的计算即可。但是,如果我需要使用 C 最大化加速,我应该如何定义输入参数 float[:] a1float[:] a2? 可能需要手动将列表转换为数组?

附:如果您还可以向我解释是否有必要明确声明 cdef float item_a2 以执行乘法(就性能而言)或者它与 result += a2[idx] * a1[idx] 相同,我将不胜感激

【问题讨论】:

标签: python cython


【解决方案1】:
cdef float calc(float[:] a1, float[:] a2):

a1a2 可以是 supports the buffer protocol 并具有 float 类型的任何对象。最常见的示例是 numpy 数组或 standard library array module。他们不会接受 Python 列表,因为 Python 列表不是有效地打包到内存中的单一同质 C 类型,而是 Python 对象的集合。

要从 Python 列表中创建合适的对象,您可以执行以下任一操作:

numpy.array([1.0,2.0],dtype=numpy.float32)
array.array('f',[1.0,2.0])

(您可能需要考虑使用double/float64 而不是float 以获得更高的精度,但这是您的选择)

如果您不想创建这样的数组对象,那么 Cython 不会对您有太大帮助,因为普通列表无法提高速度。

另一个答案中建议的 np.ndarray[FLOAT, ndim=1] a1 语法是您已经在使用的 memoryview 语法的过时版本。使用它没有任何优点(还有一些小缺点)。


result += a2[idx] * a1[idx]

很好 - Cython 知道 a1a2 的类型,因此无需创建临时中间变量。您可以使用cython -a filename.pyx 获取一个突出显示的 html 文件,以检查这将有助于指示非加速部分的位置。

【讨论】:

    【解决方案2】:

    赛通回答

    一种方法可以做到这一点(如果你愿意使用 numpy):

    import numpy as np
    cimport numpy as np
    
    ctypedef np.npy_float FLOAT
    ctypedef np.npy_intp INTP
    
    cdef FLOAT calc(np.ndarray[FLOAT, ndim=1, mode='c'] a1, 
                    np.ndarray[FLOAT, ndim=1, mode='c'] a2):
        cdef FLOAT res = 0
        cdef INTP l = a2.shape[0]
        cdef FLOAT item_a2
        cdef FLOAT item_a1
    
        for idx in range(l):
            if a2[idx] > 0:
                item_a2 = a2[idx]
                item_a1 = a1[idx]
                res += item_a2 * item_a1
    
        return res
    

    这将需要您的数组使用 np.float32 dtype。如果您想要np.float64,您可以将FLOAT 重新定义为np.float64_t

    一条不请自来的建议...l 是变量的坏名称,因为它看起来像一个数字。考虑将其重命名为 length 或类似名称。

    带有 Numpy 的纯 python

    最后,您似乎正在尝试计算两个向量之间的点积,其中一个数组中的元素为正数。您可以在这里非常有效地使用 Numpy 来获得相同的结果。

    >>> import numpy as np
    >>> a1 = np.array([0, 1, 2, 3, 4, 5, 6])
    >>> a2 = np.array([1, 2, 0, 3, -1])
    >>> a1[:a2.shape[0]].dot(np.maximum(a2, 0))
    11
    

    注意,我添加了 a1 切片,因为您没有在 Cython 函数中检查长度是否相等,而是使用了 a2 的长度。所以我假设长度可能不同。

    【讨论】:

    • 我不认为(第一部分)这是有用的建议。您已将更新、更通用的类型化 memoryview 语法更改为旧的 numpy 语法。这不会有意义地改变速度,但意味着代码实际上适用于更少输入类型。
    • @DavidW,您能说明一下它是如何在 memoryview 语法中表示的吗?
    • @Tgsmith61591,感谢您的详细回答。你能解释一下为什么你在 python 导入之后就使用cimport numpy as np 吗?这是什么原因?
    • @user1820686 OP 在问题中使用的 float[:] a1 是 memoryview 语法。 (要匹配您的mode="C",您可以将其更改为float[::1])这将接受一系列类型,包括numpy 数组、标准库array 库以及遵循Python“缓冲区协议”的任何其他类型。跨度>
    • 谢谢@DavidW。内存视图语法对我来说是新的。我一直用(显然是)旧语法编写 Cython。感谢您的澄清!
    猜你喜欢
    • 1970-01-01
    • 2013-08-02
    • 2010-12-07
    • 1970-01-01
    • 2012-02-16
    • 1970-01-01
    • 1970-01-01
    • 2011-10-02
    相关资源
    最近更新 更多