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


100

在有关reshape()函数的numpy手册中,它说

>>> a = np.zeros((10, 2))
# A transpose make the array non-contiguous
>>> b = a.T
# 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. 两者之间是否有性能差异?我们什么时候应该使用其中一个?
  3. 为什么转置会使数组不连续?
  4. 为什么会c.shape = (20)引发错误incompatible shape for a non-contiguous array

感谢您的回答!

Answers:


220

连续数组只是存储在不间断内存块中的数组:要访问数组中的下一个值,我们只需移至下一个内存地址。

考虑2D数组arr = np.arange(12).reshape(3,4)。看起来像这样:

在此处输入图片说明

在计算机的内存中,的值arr存储如下:

在此处输入图片说明

这意味着arrC连续数组,因为被存储为连续的内存块。下一个内存地址保存该行的下一行值。如果要向下移动一列,我们只需要跳过三个块(例如,从0跳到4意味着我们跳过1,2和3)。

用换位数组arr.T意味着C连续性丢失,因为相邻的行条目不再位于相邻的存储器地址中。但是,Fortranarr.T连续的,因为在内存的连续块中:

在此处输入图片说明


从性能角度来看,访问彼此相邻的内存地址通常比访问更“扩展”的地址更快(从RAM中获取值可能需要为CPU提取并缓存许多相邻地址。)意味着对连续阵列的操作通常会更快。

由于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像这样将各行放在一起:

在此处输入图片说明

shape直接设置该属性将假定C顺序-即NumPy尝试逐行执行该操作。)

这是不可能的。对于任何轴,NumPy必须具有恒定的步幅长度(要移动的字节数)才能到达数组的下一个元素。arr.T以这种方式展平将需要在内存中向前和向后跳过以检索数组的连续值。

如果我们arr2.reshape(12)改为写,NumPy会将arr2的值复制到新的内存块中(因为它无法将视图返回到该形状的原始数据)。


我对此很难理解,请您详细说明一下?在我看来,在内存中不可能排序的最新图形表示中,步幅实际上是恒定的。例如,从0到1的步幅是1个字节(假设每个元素是一个字节),并且每一列都相同。同样,从行中的一个元素到下一个元素的跨度为4个字节,并且它也是恒定的。
Vesnog

2
@Vesnog将2D失败的重塑为arr21D形状(12,)使用C顺序,这意味着将轴1在轴0之前退绕(即,四行中的每一行都必须彼此相邻放置以创建所需的1D数组)。使用恒定跨步长度(要跳转的字节)无法从缓冲区中读取此整数序列(0、4、8、1、5、9、2、6、10、3、7、11)这些元素依次为4、4,-7、4、4,-7、4、4、7、4、4)。NumPy需要每个轴的连续步幅长度。
Alex Riley

首先,我以为它会创建一个新数组,但是它使用了旧数组的内存。
Vesnog

@AlexRiley当将数组标记为按C或F顺序排序时,在幕后会发生什么?例如,获取每个NxD数组arr,然后打印(arr [:,::-1] .flags)。在这种情况下会发生什么?我想数组确实是C或F有序的,但是其中哪一个呢?如果两个标志都为False,我们会丢失哪些numpy优化?
Jjang

@Jjang:NumPy是否认为数组是C或F阶完全取决于形状和步幅(条件在此处)。因此,尽管arr[:, ::-1]与该缓冲区具有相同的视图,但arrNumPy不会将其视为C或F顺序,因为它以“非标准”顺序遍历了缓冲区中的值...
Alex Riley

12

也许此示例具有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]: x.T.flags
Out[209]: 
  C_CONTIGUOUS : False
  F_CONTIGUOUS : True
  OWNDATA : False
  ...

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]: x.T.reshape(12,)
Out[213]: array([ 0,  4,  8,  1,  5,  9,  2,  6, 10,  3,  7, 11])

您可以同时获得两者的一维视图

In [214]: x1=x.T

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...顺序,这不能被访问访问如0,4,8...在一维数组。

但是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也许也有帮助。跨步是到达下一个值必须走多远(以字节为单位)。对于2d数组,将有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)

即使x2看起来像x1,它也有自己的数据缓冲区,其值以不同的顺序排列。现在,下一列是4字节,而下一行是12(3 * 4)。

In [240]: x2.shape=(12,)

In [241]: x2.strides
Out[241]: (4,)

并且x,将形状更改为1d会将步幅减小为(4,)

因为x1,按0,1,2,...顺序排列数据,不会产生一维的跨度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具有不同的缓冲区地址。

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

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.