【问题标题】:How to make Cython much faster than Python (without Numpy) for adding two arrays together?如何使 Cython 比 Python(没有 Numpy)更快地添加两个数组?
【发布时间】:2020-03-31 11:15:26
【问题描述】:

我想使用 Cython 来减少将两个数组添加在一起(按元素)而不使用 Numpy 数组所需的时间。我发现最快的基本 Python 方法是使用列表推导,如下所示:

def add_arrays(a,b):
    return [m + n for m,n in zip(a,b)]

我的 Cython 方法有点复杂,如下所示:

from array import array
from libc.stdlib cimport malloc
from cython cimport boundscheck,wraparound

@boundscheck(False)
@wraparound(False)
cpdef add_arrays_Cython(int[:] Aarr, int[:] Barr):
    cdef size_t i, I
    I = Aarr.shape[0]
    cdef int *Carr = <int *> malloc(640000 * sizeof(int))
    for i in range(I):
        Carr[i] = Aarr[i]+Barr[i]
    result_as_array  = array('i',[e for e in Carr[:640000]])
    return result_as_array

请注意,我使用@boundscheck(False)@wraparound(False) 使其更快。 另外,我担心一个非常大的数组(大小为 640000),如果我只使用cdef int Carr[640000],我发现它会崩溃,所以我使用了malloc(),它解决了这个问题。最后,我将数据结构作为整数类型的 Python 数组返回。

为了分析我运行的代码:

a = array.array('i', range(640000)) #create integer array
b = a[:] #array to add

T=time.clock()
for i in range(20): add_arrays(a,b) #Python list comprehension approach
print(time.clock() - T)

>6.33 秒

T=time.clock()
for i in range(20): add_arrays_Cython(a,b) #Cython approach
print(time.clock() - T)

> 4.54 秒

显然,基于 Cython 的方法可以提高大约 30% 的速度。我预计加速会接近一个数量级甚至更多(就像它对 Numpy 所做的那样)。

如何进一步加快 Cython 代码的速度?我的代码中是否有任何明显的瓶颈? 我是 Cython 的初学者,所以我可能会误解一些东西。

【问题讨论】:

  • 确保描述清楚地说明您使用 Python listarray.array 的时间。 numpy 在很大程度上取代了内置的 array 包。我不知道cython 实现它的效果如何。为了最大限度地提高速度,请考虑使用 array's 缓冲区接口和 cython's typed memoryview

标签: python arrays cython


【解决方案1】:

最大的瓶颈是将结果指针转换回数组。

这是一个优化的版本:

from cython cimport boundscheck,wraparound
from cython cimport view

@boundscheck(False)
@wraparound(False)
cpdef add_arrays_Cython(int[:] Aarr, int[:] Barr):
    cdef size_t i, I
    I = Aarr.shape[0]
    result_as_array = view.array(shape=(I,), itemsize=sizeof(int), format='i')
    cdef int[:] Carr = result_as_array
    for i in range(I):
        Carr[i] = Aarr[i]+Barr[i]
    return result_as_array

这里有几件事需要注意 - 我不是 malloc 分配临时缓冲区然后将结果复制到数组,而是创建 cython.view.array 并将其转换为 int[:]。这给了我指针访问的原始速度,也避免了不必要的复制。我也直接返回 Cython 对象,而不先将其转换为 python 对象。总的来说,与您最初的 Cython 实现相比,这使我的速度提高了 70 倍。

view 对象转换为列表被证明是棘手的:如果您只是将return 语句更改为return list(result_as_array),代码将比您的初始实现慢10 倍。但是,如果您像这样添加额外的包装层:return list(memoryview(result_as_array)) 该函数比您的版本快大约 5 倍。再说一遍,主要开销是从快速原生对象到通用 python 对象,如果您需要快速代码,应该始终避免这种情况。

为了比较,我用 numpy 运行了代码。 numpy 版本的执行速度与我的 Cython 版本一样快。这意味着 C 编译器能够在我的代码中自动向量化成对求和循环。

旁注:您需要在 malloc()'d 指针上调用 free(),否则会泄漏内存。

【讨论】:

  • 如果应该返回一个列表,那么可以使用C-API构造一个列表,这将比创建一个中间数组更快。
  • @Stefan 感谢您的帮助和教学回答。事实上,我实现了大约 5 倍的加速!我希望有更多,但我想这是我应该使用视图对象的一课。
  • @ead 感谢您的建议。我尝试了 int[::1] 但它并没有太大改变速度。我还尝试使用 array.array('i',memoryview(result_as_array)) 转换为 array.array,但这会导致速度明显下降。
猜你喜欢
  • 1970-01-01
  • 2017-05-03
  • 2014-03-06
  • 1970-01-01
  • 1970-01-01
  • 2021-01-08
  • 1970-01-01
  • 2015-07-04
  • 1970-01-01
相关资源
最近更新 更多