Python Cython是否提供了任何相当简单有效的方法来迭代Numpy数组,就好像它们是平面的一样?
假设我想实现Numpy的Python Cython是否提供了任何相当简单有效的方法来迭代Numpy数组,就好像它们是平面的一样?,python,performance,numpy,cython,Python,Performance,Numpy,Cython,假设我想实现Numpy的 x[:] += 1 在赛昂。我会写 @cython.boundscheck(False) @cython.wraparoundcheck(False) def add1(np.ndarray[np.float32_t, ndim=1] x): cdef unsigned long i for i in range(len(x)): x[i] += 1 但是,这仅适用于ndim=1。我可以用 add1(x.reshape(-1)) 但
x[:] += 1
在赛昂。我会写
@cython.boundscheck(False)
@cython.wraparoundcheck(False)
def add1(np.ndarray[np.float32_t, ndim=1] x):
cdef unsigned long i
for i in range(len(x)):
x[i] += 1
但是,这仅适用于ndim=1
。我可以用
add1(x.reshape(-1))
但这只适用于连续的x
Cython是否提供了一种相当简单有效的方法来迭代Numpy数组,就好像它们是平面的一样?
(在Cython中重新实现这个特定的操作没有意义,因为上面的Numpy代码应该尽可能快——我只是把它作为一个简单的例子)
更新:
我对提议的解决方案进行了基准测试:
@cython.boundscheck(False)
@cython.wraparound(False)
def add1_flat(np.ndarray x):
cdef unsigned long i
for i in range(x.size):
x.flat[i] += 1
@cython.boundscheck(False)
@cython.wraparound(False)
def add1_nditer(np.ndarray x):
it = np.nditer([x], op_flags=[['readwrite']])
for i in it:
i[...] += 1
第二个函数除了需要cimport
之外,还需要import numpy as np
。结果是:
a = np.zeros((1000, 1000))
b = a[100:-100, 100:-100]
%timeit b[:] += 1
1000 loops, best of 3: 1.31 ms per loop
%timeit add1_flat(b)
1 loops, best of 3: 316 ms per loop
%timeit add1_nditer(b)
1 loops, best of 3: 1.11 s per loop
因此,它们比Numpy慢300倍和1000倍
更新2:
add11
版本在for
循环中使用for
循环,因此不会像平面一样迭代数组。但是,在这种情况下,它与Numpy一样快:
%timeit add1.add11(b)
1000 loops, best of 3: 1.39 ms per loop
另一方面,建议的解决方案之一
add1_unravel
无法修改b
的内容。您可以尝试使用ndarray
的flat
属性,该属性在展平的数组对象上提供迭代器。它总是以C大调顺序迭代,最后一个索引变化最快。比如:
for i in range(x.size):
x.flat[i] += 1
这是一个很好的使用nditer
的教程。它以一个cython
版本结束nditer
是numpyc
级别代码中的通用数组迭代器
Cython memoryview页面上还有一些很好的数组示例:
ndarray
的数据缓冲区是平面缓冲区。因此,无论数组的形状和步幅如何,都可以以平面c
指针方式在整个缓冲区上迭代。但是像nditer
和memoryview
这样的东西会处理元素大小的细节。因此,在c
level编码中,以平面方式逐步遍历所有元素实际上比逐行遍历更容易——逐行遍历必须考虑行的步幅
这在Python中运行,我认为它将很好地转换为cython(目前我的机器上没有这种设置):
是我不久前为模拟einsum
而编写的产品脚本的总和
其w=sop(x,y)
计算的核心是:
it = np.nditer(ops, flags, op_flags, op_axes=op_axes, order=order)
it.operands[nop][...] = 0
it.reset()
for xarr, yarr, warr in it:
x = xarr
y = yarr
w = warr
size = x.shape[0]
for i in range(size):
w[i] = w[i] + x[i] * y[i]
return it.operands[nop]
===================
从nditer.html
文档中复制想法,我得到了一个add1
版本,它的速度只有原生numpy
a+1
的一半。在cython
中,朴素的nditer
(如上)并不比在python
中快多少。许多加速可能是由于外部环路
造成的
@cython.boundscheck(False)
def add11(arg):
cdef np.ndarray[double] x
cdef int size
cdef double value
it = np.nditer([arg],
flags=['external_loop','buffered'],
op_flags=[['readwrite']])
for xarr in it:
x = xarr
size = x.shape[0]
for i in range(size):
x[i] = x[i]+1.0
return it.operands[0]
我还用python编写了这个nditer
,打印了size
,发现它在b
上迭代了78个大小为8192的块-这是缓冲区大小,不是b
及其数据布局的一些特征
In [15]: a = np.zeros((1000, 1000))
In [16]: b = a[100:-100, 100:-100]
In [17]: timeit add1.add11(b)
100 loops, best of 3: 4.48 ms per loop
In [18]: timeit b[:] += 1
100 loops, best of 3: 8.76 ms per loop
In [19]: timeit add1.add1(b) # for the unbuffered nditer
1 loop, best of 3: 3.1 s per loop
In [21]: timeit add1.add11(a)
100 loops, best of 3: 5.44 ms per loop
与
numpy.ravel(a,order='C')
返回一个连续的展开数组
返回一个包含输入元素的一维数组。副本
只有在需要时才制作
很有趣,尽管我怀疑效率很低。我稍后会运行一些测试。@MaxB似乎你对效率低下的看法是正确的。IPython中的快速
%timeit
显示,它比仅迭代数组的所有维度要多花费10%的时间。另外,我对排序的看法不正确,flat
属性总是在C排序中返回元素的迭代器。另一个选择是在x
的内存上创建一个重新成形的视图,并使用您提出的解决方案。例如,new_array=x.view().reforme(-1,1)
.AFAIK,它不适用于非连续数组(我在问题中提到过)。修改一个视图的内容,如果它不是连续的,会在写入时进行某种形式的复制,并且它查看的数组保持不变(虽然我没有在Cython中尝试过)编辑:实际上,我怀疑它在创建时复制谢谢,我将通读教程,看看它们是否回答了我的问题。我认为如果它包含了x[:]+=1
的实际实现,答案会有所改进(这是有效的,可以处理任意数量的维度和不连续的x)。我添加了一个更快的cython版本。这有点不令人满意,因为声明的目标是像平面一样迭代x
。嵌套循环并不能完全做到这一点,尽管您的解决方案可能与它得到的一样好。这里唯一的嵌套是由nditer
缓冲区大小决定的。检查我的编辑。这些计时是为了编译cython代码?@hpaulj是的,除非我搞砸了(我是cython新手)。这些定义在myfile.pyx中;setup.py包括setup(ext_modules=cythonize(“myfile.pyx”)),在将myfile导入ipython之前,我调用“python setup.py build_ext--inplace”。@hpaulj无法告诉Cython x元素的类型。如果我指定它,Cython假设ndim=1。另外,我认为flat和nditer是Cython通过pythonth调用的Python结构。这可能是合适的
In [15]: a = np.zeros((1000, 1000))
In [16]: b = a[100:-100, 100:-100]
In [17]: timeit add1.add11(b)
100 loops, best of 3: 4.48 ms per loop
In [18]: timeit b[:] += 1
100 loops, best of 3: 8.76 ms per loop
In [19]: timeit add1.add1(b) # for the unbuffered nditer
1 loop, best of 3: 3.1 s per loop
In [21]: timeit add1.add11(a)
100 loops, best of 3: 5.44 ms per loop
@cython.boundscheck(False)
@cython.wraparound(False)
def add1_ravel(np.ndarray xs):
cdef unsigned long i
cdef double[::1] aview
narray = xs.ravel()
aview = narray
for i in range(aview.shape[0]):
aview[i] += 1
# return xs if the memory is shared
if not narray.flags['OWNDATA'] or np.may_share_memory(xs, narray):
return xs
# otherwise new array reshaped
shape = tuple(xs.shape[i] for i in range(xs.ndim))
return narray.reshape(shape)