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

jde*_*eng 73 python memory arrays numpy

在关于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
Run Code Online (Sandbox Code Playgroud)

我的问题是:

  1. 什么是连续和不连续的数组?它是否类似于C中的连续内存块,比如什么是连续的内存块?
  2. 这两者之间有任何性能差异吗?我们什么时候应该使用其中一种?
  3. transpose为什么使数组不连续?
  4. 为什么c.shape = (20)抛出错误incompatible shape for a non-contiguous array

感谢您的回答!

Ale*_*ley 159

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

考虑2D阵列arr = np.arange(12).reshape(3,4).它看起来像这样:

在此输入图像描述

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

在此输入图像描述

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

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

在此输入图像描述


在性能方面,访问彼此相邻的内存地址通常比访问更"分散"的地址更快(从RAM中获取值可能需要为CPU提取和缓存大量相邻地址.)意味着连续数组上的操作通常会更快.

作为C连续内存布局的结果,行方式操作通常比列方式操作更快.例如,您通常会发现这一点

np.sum(arr, axis=1) # sum the rows
Run Code Online (Sandbox Code Playgroud)

略快于:

np.sum(arr, axis=0) # sum the columns
Run Code Online (Sandbox Code Playgroud)

同样,对于Fortran连续数组,对列的操作会稍微快一些.


最后,为什么我们不能通过分配新形状来展平Fortran连续数组?

>>> arr2 = arr.T
>>> arr2.shape = 12
AttributeError: incompatible shape for a non-contiguous array
Run Code Online (Sandbox Code Playgroud)

为了使这成为可能,NumPy必须将这些行arr.T放在一起,如下所示:

在此输入图像描述

(设置shape属性直接假定为C顺序 - 即NumPy尝试按行执行操作.)

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

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

  • @Vesnog 2D `arr2` 到 1D 形状 `(12,)` 的失败重塑使用 C 顺序,这意味着轴 1 在轴 0 之前展开(即四行中的每一行都需要彼此相邻放置)创建所需的一维数组)。使用常量步幅长度(要跳转访问的字节)从缓冲区中读取这个整数序列(0, 4, 8, 1, 5, 9, 2, 6, 10, 3, 7, 11)是不可能的这些元素的顺序为 4, 4, -7, 4, 4, -7, 4, 4, 7, 4, 4)。NumPy 要求每个轴具有恒定的步幅长度。 (4认同)
  • 我很难理解这一点,你能详细说明一下吗?在我看来,在内存中不可能的排序的最新图形表示中,实际上步幅是恒定的。例如,从 0 到 1,步长为 1 个字节(假设每个元素都是一个字节),并且每列的步长都是相同的。同样,从行中的一个元素到下一个元素的步幅是 4 个字节,并且它也是恒定的。 (3认同)

hpa*_*ulj 8

也许这个有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
  ...
Run Code Online (Sandbox Code Playgroud)

这些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])
Run Code Online (Sandbox Code Playgroud)

您可以获得两者的1d视图

In [214]: x1=x.T

In [217]: x.shape=(12,)
Run Code Online (Sandbox Code Playgroud)

形状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
Run Code Online (Sandbox Code Playgroud)

但转置的形状无法改变.在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,)
Run Code Online (Sandbox Code Playgroud)

strides也许也有帮助.一个步骤是它必须步骤到达下一个值的距离(以字节为单位).对于2d数组,将有2个步幅值:

In [233]: x=np.arange(12).reshape(3,4).copy()

In [234]: x.strides
Out[234]: (16, 4)
Run Code Online (Sandbox Code Playgroud)

要到达下一行,步骤16字节,下一列只有4.

In [235]: x1.strides
Out[235]: (4, 16)
Run Code Online (Sandbox Code Playgroud)

转置只是切换步幅的顺序.下一行只有4个字节 - 即下一个数字.

In [236]: x.shape=(12,)

In [237]: x.strides
Out[237]: (4,)
Run Code Online (Sandbox Code Playgroud)

更改形状也会改变步幅 - 一次只需逐步通过缓冲区4个字节.

In [238]: x2=x1.copy()

In [239]: x2.strides
Out[239]: (12, 4)
Run Code Online (Sandbox Code Playgroud)

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

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

In [241]: x2.strides
Out[241]: (4,)
Run Code Online (Sandbox Code Playgroud)

而且x,将形状改为1d会减少步幅(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')]}
Run Code Online (Sandbox Code Playgroud)

x1数据缓冲器地址将是相同x,同它的数据. x2有一个不同的缓冲区地址.

您还可以尝试向和命令添加order='F'参数.copyreshape