【问题标题】:Iterating over arrays in cython, is list faster than np.array?迭代cython中的数组,列表比np.array快吗?
【发布时间】:2013-09-27 02:22:19
【问题描述】:

TLDR:在 cython 中,为什么(或何时?)迭代 numpy 数组比迭代 python 列表更快?

通常: 我以前使用过 Cython,并且能够在幼稚的 python impl' 上获得巨大的加速, 然而,弄清楚究竟需要做什么似乎并非易事。

考虑以下 3 个 sum() 函数的实现。 它们驻留在一个名为“cy”的 cython 文件中(显然,有 np.sum(),但这不是我的意思..)

天真的蟒蛇:

def sum_naive(A):
   s = 0
   for a in A:
       s += a
   return s

具有需要 python 列表的函数的 Cython:

def sum_list(A):
    cdef unsigned long s = 0
    for a in A:
        s += a
    return s

具有需要 numpy 数组的函数的 Cython。

def sum_np(np.ndarray[np.int64_t, ndim=1] A):
    cdef unsigned long s = 0
    for a in A:
        s += a
    return s

我希望就运行时间而言,sum_np ,但是,下面的脚本却相反(为了完整性,我添加了 np.sum() )

N = 1000000
v_np = np.array(range(N))
v_list = range(N)

%timeit cy.sum_naive(v_list)
%timeit cy.sum_naive(v_np)
%timeit cy.sum_list(v_list)
%timeit cy.sum_np(v_np)
%timeit v_np.sum()

结果:

In [18]: %timeit cyMatching.sum_naive(v_list)
100 loops, best of 3: 18.7 ms per loop

In [19]: %timeit cyMatching.sum_naive(v_np)
1 loops, best of 3: 389 ms per loop

In [20]: %timeit cyMatching.sum_list(v_list)
10 loops, best of 3: 82.9 ms per loop

In [21]: %timeit cyMatching.sum_np(v_np)
1 loops, best of 3: 1.14 s per loop

In [22]: %timeit v_np.sum()
1000 loops, best of 3: 659 us per loop

发生了什么事? 为什么 cython+numpy 慢?

附言
我确实使用
#cython: boundscheck=False
#cython: wraparound=False

【问题讨论】:

  • 我不知道 cython 是否可以加快数组的 for a in A 类型循环,也许输入 a 会解决问题。我相信,尽管正确(或至少更常见)加快速度的方法是:cdef int j; for j in range(len(A)): s += A[j].

标签: python arrays optimization numpy cython


【解决方案1】:

在 cython 中有一种更好的方法来实现这一点,至少在我的机器上优于 np.sum,因为它避免了类型检查和 numpy 在处理任意数组时通常必须做的其他事情:

#cython.wraparound=False
#cython.boundscheck=False
cimport numpy as np

def sum_np(np.ndarray[np.int64_t, ndim=1] A):
    cdef unsigned long s = 0
    for a in A:
        s += a
    return s

def sum_np2(np.int64_t[::1] A):
    cdef:
        unsigned long s = 0
        size_t k

    for k in range(A.shape[0]):
        s += A[k]

    return s

然后是时间安排:

N = 1000000
v_np = np.array(range(N))
v_list = range(N)

%timeit sum(v_list)
%timeit sum_naive(v_list)
%timeit np.sum(v_np)
%timeit sum_np(v_np)
%timeit sum_np2(v_np)
10 loops, best of 3: 19.5 ms per loop
10 loops, best of 3: 64.9 ms per loop
1000 loops, best of 3: 1.62 ms per loop
1 loops, best of 3: 1.7 s per loop
1000 loops, best of 3: 1.42 ms per loop

您不想通过 Python 样式迭代 numpy 数组,而是使用索引访问元素,因为它可以转换为纯 C,而不是依赖 Python API。

【讨论】:

  • 对于一维数组,使用[::1] 是否比[:] 有任何切实的好处?
  • 我收回了我之前的评论。我不确定为什么我得到了这样的改进,但它不可重现。现在我得到大约 650us 和 621us。谢谢乔希! (以及 size_t 的 +1)
  • [::1] 表示内存在该维度上是连续的,并且对于我的机器上的这个示例似乎确实产生了微小的差异。
  • 它降低了该方法的灵活性,因为您不能再像sum_np2(v_np[::3]) 那样将跨步切片传递给它,而不会收到错误提示数组未布置在内存中的连续块中。
【解决方案2】:

a 是无类型的,因此会有很多从 Python 到 C 类型的转换。这些可能很慢。

JoshAdel 正确地指出,您应该迭代一个范围,而不是迭代。 Cython 会将索引转换为 C,速度很快。


使用cython -a myfile.pyx 将为您突出显示这些内容;您希望所有循环逻辑都为白色以获得最大速度。

PS:请注意,np.ndarray[np.int64_t, ndim=1] 已过时,已被弃用,取而代之的是更快、更通用的long[:]

【讨论】:

    猜你喜欢
    • 2011-09-05
    • 1970-01-01
    • 2021-11-27
    • 1970-01-01
    • 2015-02-27
    • 1970-01-01
    • 2012-01-31
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多