连续和非连续数组之间有什么区别?
在关于reshape()函数的numpy手册中 ,它说
>>> a = np.zeros((10, 2)) # A transpose make the array non-contiguous >>> b = aT # Taking a view makes it possible to modify the shape without modifying the # initial object. >>> c = b.view() >>> c.shape = (20) AttributeError: incompatible shape for a non-contiguous array
我的问题是:
- 什么是连续和不连续的数组? 它与C中的连续内存块相似吗?什么是连续内存块?
- 这两者之间有什么performance差异? 我们应该什么时候使用一个或另一个?
- 为什么转置使数组不连续?
- 为什么
c.shape = (20)
incompatible shape for a non-contiguous array
抛出错误incompatible shape for a non-contiguous array
?
感谢您的回答!
一个连续的数组只是存储在一个完整的内存块中的一个数组:要访问数组中的下一个值,我们只是移动到下一个内存地址。
考虑二维数组arr = np.arange(12).reshape(3,4)
。 它看起来像这样:
在计算机的内存中, arr
的值是这样存储的:
这意味着arr
是一个C连续的数组,因为行被存储为连续的内存块。 下一个内存地址保存该行的下一行值。 如果我们想要向下移动一列,我们只需要跳过三个块(例如从0跳到4就意味着跳过1,2和3)。
使用arr.T
转置数组意味着C邻接会丢失,因为相邻的行条目不再位于相邻的存储器地址中。 然而, arr.T
是Fortran连续的,因为列在连续的内存块中:
从性能上看,访问彼此相邻的内存地址通常比访问更“分散”的地址要快(从RAM中获取值可能需要为CPU获取和caching大量相邻地址)。意味着通过连续arrays的操作通常会更快。
由于C连续内存布局的结果,行方式操作通常比列方式操作更快。 例如,你通常会发现
np.sum(arr, axis=1) # sum the rows
比以下要快一些:
np.sum(arr, axis=0) # sum the columns
同样,对于Fortran连续数组,列上的操作也会稍微快一点。
最后,为什么我们不能通过分配一个新形状来压缩Fortran连续数组?
>>> arr2 = arr.T >>> arr2.shape = 12 AttributeError: incompatible shape for a non-contiguous array
为了这个可能,NumPy必须像这样将arr.T
的行arr.T
一起:
(直接设置shape
属性假定C顺序 – 即NumPy尝试按行执行操作。)
这是不可能的。 对于任何轴,NumPy需要有一个固定的步长(移动的字节数)才能到达数组的下一个元素。 以这种方式展平arr.T
将需要在存储器中向前和向后跳过来检索数组的连续值。
如果我们写了arr2.reshape(12)
,NumPy会将arr2的值复制到一个新的内存块中(因为它不能将视图返回到此形状的原始数据)。
也许这个例子有12个不同的数组值将有助于:
In [207]: x=np.arange(12).reshape(3,4).copy() In [208]: x.flags Out[208]: C_CONTIGUOUS : True F_CONTIGUOUS : False OWNDATA : True ... In [209]: xTflags Out[209]: C_CONTIGUOUS : False F_CONTIGUOUS : True OWNDATA : False ...
C order
值按照它们生成的顺序排列。颠倒的C order
值不是
In [212]: x.reshape(12,) # same as x.ravel() Out[212]: array([ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]) In [213]: xTreshape(12,) Out[213]: array([ 0, 4, 8, 1, 5, 9, 2, 6, 10, 3, 7, 11])
你可以得到1d的观点
In [214]: x1=xT In [217]: x.shape=(12,)
x
的形状也可以改变。
In [220]: x1.shape=(12,) --------------------------------------------------------------------------- AttributeError Traceback (most recent call last) <ipython-input-220-cf2b1a308253> in <module>() ----> 1 x1.shape=(12,) AttributeError: incompatible shape for a non-contiguous array
但转置的形状是不能改变的。 data
仍然是0,1,2,3,4...
顺序,无法在1d数组中以0,4,8...
forms访问。
但是可以更改x1
的副本:
In [227]: x2=x1.copy() In [228]: x2.flags Out[228]: C_CONTIGUOUS : True F_CONTIGUOUS : False OWNDATA : True ... In [229]: x2.shape=(12,)
看着strides
也可能有所帮助。 步幅是多less(以字节为单位),它必须步骤到达下一个值。 对于二维数组,将会有2个步长值:
In [233]: x=np.arange(12).reshape(3,4).copy() In [234]: x.strides Out[234]: (16, 4)
要到下一行,第16步,下一列只有4。
In [235]: x1.strides Out[235]: (4, 16)
移调只是切换步幅的顺序。 下一行只有4个字节,即下一个数字。
In [236]: x.shape=(12,) In [237]: x.strides Out[237]: (4,)
改变形状也会改变步幅 – 一次只需要通过缓冲区4个字节。
In [238]: x2=x1.copy() In [239]: x2.strides Out[239]: (12, 4)
尽pipex2
看起来就像x1
一样,但是它有自己的数据缓冲区,值的顺序不同。 下一列现在是4个字节,而下一行是12(3 * 4)。
In [240]: x2.shape=(12,) In [241]: x2.strides Out[241]: (4,)
和x
,将形状改为1d可以将步幅减less到(4,)
。
对于x1
,数据在0,1,2,...
顺序中,没有1d的步长会给0,4,8...
__array_interface__
是另一种显示数组信息的有用方法:
In [242]: x1.__array_interface__ Out[242]: {'strides': (4, 16), 'typestr': '<i4', 'shape': (4, 3), 'version': 3, 'data': (163336056, False), 'descr': [('', '<i4')]}
x1
数据缓冲区地址将与x
共享数据的地址相同。 x2
有不同的缓冲区地址。
您也可以尝试为copy
和reshape
命令添加一个order='F'
参数。