为什么Cython比矢量化NumPy慢?

时间:2014-06-19 13:48:58

标签: python arrays performance numpy cython

考虑以下Cython代码:

cimport cython
cimport numpy as np
import numpy as np

@cython.boundscheck(False)
@cython.wraparound(False)
def test_memoryview(double[:] a, double[:] b):
    cdef int i
    for i in range(a.shape[0]):
        a[i] += b[i]

@cython.boundscheck(False)
@cython.wraparound(False)
def test_numpy(np.ndarray[double, ndim=1] a, np.ndarray[double, ndim=1] b):
    cdef int i
    for i in range(a.shape[0]):
        a[i] += b[i]

def test_numpyvec(a, b):
    a += b

def gendata(nb=40000000):
    a = np.random.random(nb)
    b = np.random.random(nb)
    return a, b

在解释器中运行它(在几次运行后预热缓存):

In [14]: %timeit -n 100 test_memoryview(a, b)
100 loops, best of 3: 148 ms per loop

In [15]: %timeit -n 100 test_numpy(a, b)
100 loops, best of 3: 159 ms per loop

In [16]: %timeit -n 100 test_numpyvec(a, b)
100 loops, best of 3: 124 ms per loop

# See answer below :
In [17]: %timeit -n 100 test_raw_pointers(a, b)
100 loops, best of 3: 129 ms per loop

我尝试使用不同的数据集大小,并且一致地使矢量化NumPy函数比编译的Cython代码运行得更快,而我期望Cython在矢量化NumPy方面与性能相当。

我是否忘记了我的Cython代码中的优化? NumPy是否会使用某些东西(BLAS?)来使这些简单的操作运行得更快?我可以改进此代码的性能吗?

更新:原始指针版本似乎与NumPy相同。显然,在使用内存视图或NumPy索引时会出现一些开销。

3 个答案:

答案 0 :(得分:10)

另一种选择是使用原始指针(以及全局指令以避免重复@cython...):

#cython: wraparound=False
#cython: boundscheck=False
#cython: nonecheck=False

#...

cdef ctest_raw_pointers(int n, double *a, double *b):
    cdef int i
    for i in range(n):
        a[i] += b[i]

def test_raw_pointers(np.ndarray[double, ndim=1] a, np.ndarray[double, ndim=1] b):
    ctest_raw_pointers(a.shape[0], &a[0], &b[0])

答案 1 :(得分:3)

在我的机器上,差异并不大,但我可以通过更改像这样的numpy和内存视图函数来消除它

@cython.boundscheck(False)
@cython.wraparound(False)
def test_memoryview(double[:] a, double[:] b):
    cdef int i, n=a.shape[0]
    for i in range(n):
        a[i] += b[i]

@cython.boundscheck(False)
@cython.wraparound(False)
def test_numpy(np.ndarray[double] a, np.ndarray[double] b):
    cdef int i, n=a.shape[0]
    for i in range(n):
        a[i] += b[i]

然后,当我从Cython编译C输出时,我使用标志-O3-march=native。 这似乎表明时序的差异来自于使用不同的编译器优化。

我使用64位版本的MinGW和NumPy 1.8.1。 您的结果可能会因软件包版本,硬件,平台和编译器而异。

如果您正在使用IPython笔记本的Cython魔术,则可以使用%%cython

替换%%cython -f -c=-O3 -c=-march=native来强制使用其他编译器标记进行更新

如果您正在为cython模块使用标准setup.py,则可以在创建传递给extra_compile_args的Extension对象时指定distutils.setup参数。

注意:在指定NumPy数组的类型时,我删除了ndim=1标志,因为它不是必需的。 无论如何,该值默认为1.

答案 2 :(得分:1)

稍微提高速度的变化是指定步幅:

def test_memoryview_inorder(double[::1] a, double[::1] b):
    cdef int i
    for i in range(a.shape[0]):
        a[i] += b[i]