预测 ufunc 输出的内存布局

Pau*_*zer 5 python numpy memory-layout numpy-ufunc

ndarray大多数时候使用 numpy s 我们不需要担心我们漂亮的小脑袋关于内存布局,因为结果不依赖于它。

除非他们这样做。例如,考虑这种设置 3x2 矩阵对角线的稍微过度设计的方法

>>> a = np.zeros((3,2))
>>> a.reshape(2,3)[:,0] = 1
>>> a
array([[1., 0.],
       [0., 1.],
       [0., 0.]])
Run Code Online (Sandbox Code Playgroud)

只要我们控制好内存布局a就可以了。但是,如果我们不这样做,那就是一个错误,更糟糕的是,这是一个令人讨厌的无声错误:

>>> a = np.zeros((3,2),order='F')
>>> a.reshape(2,3)[:,0] = 1
>>> a
array([[0., 0.],
       [0., 0.],
       [0., 0.]])
Run Code Online (Sandbox Code Playgroud)

这足以表明内存布局不仅仅是一个实现细节。

为了了解数组布局,人们可能合理地要求的第一件事是新数组是什么样的?这些工厂emptyoneszerosidentity每默认等回报C-连续布局。

但是,此规则并未扩展到 numpy 分配的每个新数组。例如:

>>> a = np.arange(8).reshape(2,2,2).transpose(1,0,2)
>>> aa = a*a
Run Code Online (Sandbox Code Playgroud)

乘积aa是 ufunc 分配的新数组np.multiply。它是 C 连续的吗?不:

>>> aa.strides
(16, 32, 8)
Run Code Online (Sandbox Code Playgroud)

我的猜测是,这是优化的结果,该优化认识到可以在平面线性阵列上完成此操作,这将解释为什么输出具有与输入相同的内存布局。

事实上,这甚至很有用,不像下面的废话函数。它显示了一个方便的习惯用法来实现轴参数,同时仍然保持索引简单。

>>> def symmetrize_along_axis(a,axis=0):
...     aux = a.swapaxes(0,axis)
...     out = aux + aux[::-1]
...     return out.swapaxes(0,axis)
Run Code Online (Sandbox Code Playgroud)

稍微令人惊讶但显然是可取的事情是,只要输入是连续的,就会产生连续的输出。

>>> a = np.arange(8).reshape(2,2,2)
>>> symmetrize_along_axis(a,1).flags.contiguous
True
Run Code Online (Sandbox Code Playgroud)

这足以表明了解 ufunc 返回的布局非常有用。因此我的问题是:

鉴于 ufunc 参数的布局,是否有关于输出布局的任何规则或保证?

hpa*_*ulj 3

以防a = np.zeros((3,2),order='F')万一,a.reshape(2,3)创建副本,而不是视图。这就是分配失败的原因,而不是内存布局本身。

查看相同形状的数组:

In [123]: a = np.arange(6).reshape(3,2)                                                              
In [124]: a                                                                                          
Out[124]: 
array([[0, 1],
       [2, 3],
       [4, 5]])
In [125]: a.reshape(2,3)                                                                             
Out[125]: 
array([[0, 1, 2],
       [3, 4, 5]])
In [127]: a.reshape(2,3)[:,0]                                                                        
Out[127]: array([0, 3])
Run Code Online (Sandbox Code Playgroud)

在[125]中,值仍然按顺序 C 流动。

和一个 F 阶数组:

In [128]: b = np.arange(6).reshape(3,2, order='F')                                                   
In [129]: b                                                                                          
Out[129]: 
array([[0, 3],                 # values flow in order F
       [1, 4],
       [2, 5]])
In [130]: b.reshape(2,3)                                                                             
Out[130]: 
array([[0, 3, 1],              # values are jumbled
       [4, 2, 5]])
In [131]: b.reshape(2,3)[:,0]                                                                        
Out[131]: array([0, 4])
Run Code Online (Sandbox Code Playgroud)

如果我保持订单 F 的形状:

In [132]: b.reshape(2,3, order='F')                                                                  
Out[132]: 
array([[0, 2, 4],               # values still flow in order F
       [1, 3, 5]])
In [133]: b.reshape(2,3, order='F')[:,0]                                                             
Out[133]: array([0, 1])
Run Code Online (Sandbox Code Playgroud)

确认分配:

In [135]: a.reshape(2,3)[:,0]=10                                                                     
In [136]: a                                                                                          
Out[136]: 
array([[10,  1],
       [ 2, 10],
       [ 4,  5]])
Run Code Online (Sandbox Code Playgroud)

不是赋值:

In [137]: b.reshape(2,3)[:,0]=10                                                                     
In [138]: b                                                                                          
Out[138]: 
array([[0, 3],
       [1, 4],
       [2, 5]])
Run Code Online (Sandbox Code Playgroud)

但这里的分配有效:

In [139]: b.reshape(2,3, order='F')[:,0]=10                                                          
In [140]: b                                                                                          
Out[140]: 
array([[10,  3],
       [10,  4],
       [ 2,  5]])
Run Code Online (Sandbox Code Playgroud)

或者我们可以使用 orderA来保留顺序:

In [143]: b.reshape(2,3, order='A')[:,0]                                                             
Out[143]: array([10, 10])
In [144]: b.reshape(2,3, order='A')[:,0] = 20                                                        
In [145]: b                                                                                          
Out[145]: 
array([[20,  3],
       [20,  4],
       [ 2,  5]])
Run Code Online (Sandbox Code Playgroud)

ufunc顺序

怀疑ufunc(大部分)是用nditer(C 版本)实现的,我检查了 np.nditer` 文档 - 可以在几个地方指定顺序。本教程演示了顺序对迭代的影响。

我没有看到order的记录ufunc,但是,它被kwargs.

In [171]: c = np.arange(8).reshape(2,2,2)                                                            
In [172]: d = c.transpose(1,0,2)                                                                     
In [173]: d.strides                                                                                  
Out[173]: (16, 32, 8)
In [174]: np.multiply(d,d,order='K').strides                                                         
Out[174]: (16, 32, 8)
In [175]: np.multiply(d,d,order='C').strides                                                         
Out[175]: (32, 16, 8)
In [176]: np.multiply(d,d,order='F').strides                                                         
Out[176]: (8, 16, 32)
Run Code Online (Sandbox Code Playgroud)