连续和非连续数组之间有什么区别?

在关于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 

我的问题是:

  1. 什么是连续和不连续的数组? 它与C中的连续内存块相似吗?什么是连续内存块?
  2. 这两者之间有什么performance差异? 我们应该什么时候使用一个或另一个?
  3. 为什么转置使数组不连续?
  4. 为什么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.TFortran连续的,因为在连续的内存块中:

在这里输入图像说明


从性能上看,访问彼此相邻的内存地址通常比访问更“分散”的地址要快(从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有不同的缓冲区地址。

您也可以尝试为copyreshape命令添加一个order='F'参数。