考虑这样的代码:
import numpy as np
cimport numpy as np
cdef inline inc(np.ndarray[np.int32_t] arr, int i):
arr[i]+= 1
def test1(np.ndarray[np.int32_t] arr):
cdef int i
for i in xrange(len(arr)):
inc(arr, i)
def test2(np.ndarray[np.int32_t] arr):
cdef int i
for i in xrange(len(arr)):
arr[i] += 1
我用ipython来测量test1和test2的速度:
In [7]: timeit ttt.test1(arr)
100 loops, best of 3: 6.13 ms per loop
In [8]: timeit ttt.test2(arr)
100000 loops, best of 3: 9.79 us per loop
有没有办法优化test1?为什么cython不按照上面说的那样内联这个函数?
更新: 实际上,我需要的是这样的多维代码:
# cython: infer_types=True
# cython: boundscheck=False
# cython: wraparound=False
import numpy as np
cimport numpy as np
cdef inline inc(np.ndarray[np.int32_t, ndim=2] arr, int i, int j):
arr[i, j] += 1
def test1(np.ndarray[np.int32_t, ndim=2] arr):
cdef int i,j
for i in xrange(arr.shape[0]):
for j in xrange(arr.shape[1]):
inc(arr, i, j)
def test2(np.ndarray[np.int32_t, ndim=2] arr):
cdef int i,j
for i in xrange(arr.shape[0]):
for j in xrange(arr.shape[1]):
arr[i,j] += 1
时间安排:
In [7]: timeit ttt.test1(arr)
1 loops, best of 3: 647 ms per loop
In [8]: timeit ttt.test2(arr)
100 loops, best of 3: 2.07 ms per loop
显式内联使速度提高了300倍。而且我的实际函数很大,因此内联会使代码的可维护性更差
更新2:
# cython: infer_types=True
# cython: boundscheck=False
# cython: wraparound=False
import numpy as np
cimport numpy as np
cdef inline inc(np.ndarray[np.float32_t, ndim=2] arr, int i, int j):
arr[i, j]+= 1
def test1(np.ndarray[np.float32_t, ndim=2] arr):
cdef int i,j
for i in xrange(arr.shape[0]):
for j in xrange(arr.shape[1]):
inc(arr, i, j)
def test2(np.ndarray[np.float32_t, ndim=2] arr):
cdef int i,j
for i in xrange(arr.shape[0]):
for j in xrange(arr.shape[1]):
arr[i,j] += 1
cdef class FastPassingFloat2DArray(object):
cdef float* data
cdef int stride0, stride1
def __init__(self, np.ndarray[np.float32_t, ndim=2] arr):
self.data = <float*>arr.data
self.stride0 = arr.strides[0]/arr.dtype.itemsize
self.stride1 = arr.strides[1]/arr.dtype.itemsize
def __getitem__(self, tuple tp):
cdef int i, j
cdef float *pr, r
i, j = tp
pr = (self.data + self.stride0*i + self.stride1*j)
r = pr[0]
return r
def __setitem__(self, tuple tp, float value):
cdef int i, j
cdef float *pr, r
i, j = tp
pr = (self.data + self.stride0*i + self.stride1*j)
pr[0] = value
cdef inline inc2(FastPassingFloat2DArray arr, int i, int j):
arr[i, j]+= 1
def test3(np.ndarray[np.float32_t, ndim=2] arr):
cdef int i,j
cdef FastPassingFloat2DArray tmparr = FastPassingFloat2DArray(arr)
for i in xrange(arr.shape[0]):
for j in xrange(arr.shape[1]):
inc2(tmparr, i,j)
时间安排:
In [4]: timeit ttt.test1(arr)
1 loops, best of 3: 623 ms per loop
In [5]: timeit ttt.test2(arr)
100 loops, best of 3: 2.29 ms per loop
In [6]: timeit ttt.test3(arr)
1 loops, best of 3: 201 ms per loop
将数组作为类型为
numpy.ndarray
的Python对象传递给inc()
。由于引用计数等问题,传递Python对象非常昂贵,而且似乎会阻止内联。如果以C方式传递数组,即作为指针,test1()
甚至比我的机器上的test2()
更快:问题是,分配numpy数组(或者,等价地,将其作为函数参数传入)不仅仅是一个简单的赋值,而是一个“缓冲区提取”,它填充结构并将跨距和指针信息提取到快速索引所需的局部变量中。如果迭代的元素数量适中,那么这个O(1)开销很容易在循环中分摊,但对于小函数来说肯定不是这样。
改善这一点是很多人的心愿,但这是一个非常重要的变化。例如,见http://groups.google.com/group/cython-users/browse_thread/thread/8fc8686315d7f3fe的讨论
发帖至今已有3年多的时间,同时也取得了很大的进展。关于此代码(问题的更新2):
我有以下时间安排:
因此,即使超过3年,这个问题还是可以重现的。Cython现在有typed memoryviews,因为它是Cython 0.16中引入的,所以在发布问题时不可用。有了这个:
有了这个我得到:
我们几乎快到那里了,而且已经比老式的方式快了!现在,} ,所以让我们声明它!但糟糕的是:
inc()
函数有资格声明^{啊,我完全错过了
void
返回类型的丢失!再一次但是现在用void
:最后我得到:
和手动内联一样快!
为了好玩,我试过Numba这段代码:
我得到:
尽管它比Cython慢4.7倍,很可能是因为JIT编译器未能内联
inc()
,但我认为它是非常棒的!我需要做的就是添加@autojit
,而不必用笨拙的类型声明来搅乱代码;几乎不需要任何东西就可以加速88x!我也试过和努玛一起做其他事情,比如
或
nopython=True
但未能进一步改善。Improving inlining is on the Numba developers' list,我们只需要提交更多的请求就可以使它具有更高的优先级。;)
相关问题 更多 >
编程相关推荐