Numpy多维数组的内存设计与实现原理

1、内存设计与实现原理

ndarray的内存结构

ndarray的实例本质上由一个连续的一维内存段一个索引方案组合而成。这种将所有数据存放在一个连续的一维内存段的存储方式,实际与C语言中的多维数组存储方式一致。但Numpy索引的灵活设计,使得ndarray对象可适应于任何跨步索引方案,以下对ndarray对象以行作为主要存储顺序的内存设计进行说明。

Numpy在创建数组或建立数组视图时,将数组的信息记录在不同属性,如shape属性指定各维度的元素的数量,dtype属性指定元素类型及其解释方式,strides属性指定各维度的跨度,itemsize属性指定单个元素占用字节数。

图1 ndarray对象在内存中的存储与索引

索引的解析

对于 N N N维的数组 a r r a y \sf array array,将各维度的跨度存放在 s t r i d e s \sf{strides} strides列表中,则第 k + 1 k+1 k+1维的跨度
s t r i d e s [ k ] = { i t e m s i z e , k = N − 1 i t e m s i z e × ∏ j = k + 1 N − 1 s h a p e [ j ] , k = 0 , ⋯   , N − 2 {\sf strides}[k] = \begin{cases} {\sf{itemsize}}, &k=N-1\\ {\sf{itemsize}} \times \prod_{j=k+1}^{N-1}{\sf{shape}}[j], &k=0,\cdots,N-2\\ \end{cases} strides[k]={itemsize,itemsize×j=k+1N1shape[j],k=N1k=0,,N2

令数组 a r r a y \sf array array的首元素地址为 & a r r a y \&{\sf{array}} &array,则数组中坐标为 ( i 0 , i 1 , ⋯   , i N − 1 ) (i_0,i_1,\cdots,i_{N-1}) (i0,i1,,iN1)的元素的地址
& a r r a y [ i 0 ] [ i 1 ] ⋯ [ i N − 1 ] = & a r r a y + ∑ k = 0 N − 1 i k × s t r i d e s [ k ] \&{\sf{array}}[i_0][i_1]\cdots[i_{N-1}]= \&{\sf{array}} + \sum_{k=0}^{N-1}i_{k}\times {\sf{strides}}[k] &array[i0][i1][iN1]=&array+k=0N1ik×strides[k]

利用以上公式计算出给定坐标对应的地址,即 & a r r a y [ i 0 ] [ i 1 ] ⋯ [ i N − 1 ] \&{\sf{array}}[i_0][i_1]\cdots[i_{N-1}] &array[i0][i1][iN1],然后即可得到 a r r a y [ i 0 ] [ i 1 ] ⋯ [ i N − 1 ] {\sf{array}}[i_0][i_1]\cdots[i_{N-1}] array[i0][i1][iN1]的值。

实例

对于数据类型为int32(占4字节)的二维整型数组
b = ( 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 ) \bm b = \left(\begin{matrix} 0 &1 &2 &3 \\ 4 &5 &6 &7 \\ 8 &{\bm\color {red}9}&10 &11 \\ 12 &13 &14 &15 \end{matrix}\right) b=0481215913261014371115

其形状 s h a p e = ( 4 , 4 ) {\sf shape}=(4,4) shape=(4,4) i t e m s i z e = 4 {\sf itemsize}=4 itemsize=4,因此不难计算出跨度列表 s t r i d e s = [ 16 , 4 ] {\sf strides}=[16, 4] strides=[16,4]

若以 b [ 2 ] [ 1 ] b[2][1] b[2][1]的索引方式访问元素 9 9 9,实际底层是将引用地址解析为
& b [ 2 ] [ 1 ] = & b + 2 × s t r i d e s [ 0 ] + 1 × s t r i d e s [ 1 ] = & b + 36 \&b[2][1] = \&b + 2 \times {\sf strides}[0] + 1\times {\sf strides}[1] = \&b+36 &b[2][1]=&b+2×strides[0]+1×strides[1]=&b+36

显然,地址 & b + 36 \&b+36 &b+36存储的值为目标元素 9 9 9,即完成了索引的解析。

2、切片索引的视图原理

切片索引的解析

对于二维数组 b b b,使用以下起始值及步长对其进行切片
a = b [ b e g i n 0 : e n d 0 : s t e p 0 , b e g i n 1 : e n d 1 : s t e p 1 ] a = b[{\sf begin_0:end_0:step_0}, {\sf begin_1:end_1:step_1}] a=b[begin0:end0:step0,begin1:end1:step1]

易知,结果数组 a a a的首地址
& a = & b [ b e g i n 0 , b e g i n 1 ] = & b + b e g i n 0 × s t r i d e s [ 0 ] + b e g i n 1 × s t r i d e s [ 1 ] \&a=\&b[{\sf begin_0}, {\sf begin_1}]=\&b+{\sf begin_0}\times {\sf strides}[0] + {\sf begin_1}\times {\sf strides}[1] &a=&b[begin0,begin1]=&b+begin0×strides[0]+begin1×strides[1]

基于各维度的切片步长,更新切片索引结果数组 a a a的跨度列表
s t r i d e s ′ = s t e p × s t r i d e s = [ s t e p 0 , s t e p 1 ] × s t r i d e s {\sf strides'} = {\sf step} \times {\sf strides} = [{\sf step_0}, {\sf step_1}] \times {\sf strides} strides=step×strides=[step0,step1]×strides

此时,利用结果数组 a a a的起始地址 & a \&a &a以及新的跨度列表 s t r i d e s ′ {\sf strides'} strides,使用索引 a [ i ] [ j ] a[i][j] a[i][j]的形式依然可以访问到目标元素。

切片索引要求每一维度具有固定的切片步长,因此我们仅需要创建一个原始数组的视图,并根据起始位置以及各维度的切片步长,修改视图的起始位置以及各维度的跨度,即可访问到结果数组中的目标元素。因此,切片索引不需要复制原始数据。由于整数数组索引步数的随机性,不能通过更改索引方案的方案访问原数组。因此,整数数组索引返回的是原始数组的副本。

实例

对于数组 b b b,执行切片 a = b [ : : 3 , 1 : : 2 ] a = b[::3, 1::2] a=b[::3,1::2],得到红色标记位置元素
b = ( 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 ) , a = ( 1 3 13 15 ) \bm b = \left(\begin{matrix} 0 &{\bm\color {red}1} &2 &{\bm\color {red}3} \\ 4 &5 &6 &7 \\ 8 &9 &10 &11 \\ 12 &{\bm\color {red}13} &14 &{\bm\color {red}15} \end{matrix}\right), \quad\bm a=\left(\begin{matrix} 1 &3\\ 13 &15 \end{matrix}\right) b=0481215913261014371115,a=(113315)

原数组 b b b的跨度列表 s t r i d e s = [ 16 , 4 ] {\sf strides}=[16, 4] strides=[16,4],结果数组 a a a的首地址 & a = & b + 4 \&a=\&b+4 &a=&b+4,结果数组 a a a的跨度列表 s t r i d e s ′ = [ 48 , 8 ] {\sf strides'}=[48, 8] strides=[48,8]

通过索引 a [ 1 , 1 ] a[1,1] a[1,1]的方式访问元素15,实际底层将引用地址解析为
& a [ 1 ] [ 1 ] = & a + 1 × s t r i d e s ′ [ 0 ] + 1 × s t r i d e s ′ [ 1 ] = & a + 56 = & b + 60 \&a[1][1] = \&a + 1 \times {\sf strides'}[0] + 1\times {\sf strides'}[1] = \&a+56 = \&b+60 &a[1][1]=&a+1×strides[0]+1×strides[1]=&a+56=&b+60

此外
& b [ 3 ] [ 3 ] = & b + 3 × s t r i d e s [ 0 ] + 3 × s t r i d e s [ 1 ] = & b + 60 \&b[3][3] = \&b + 3 \times {\sf strides}[0] + 3\times {\sf strides}[1] = \&b+60 &b[3][3]=&b+3×strides[0]+3×strides[1]=&b+60

显然,使用切片索引仅通过更改索引方案,即可访问到原始数据。

  • 2
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值