理解numpy中ndarray的内存布局和设计哲学
本文的主要目的在于理解numpy.ndarray
的内存结构及其背后的设计哲学。
ndarray是什么
NumPy provides an N-dimensional array type, the ndarray, which describes a collection of “items” of the same type. The items can be indexed using for example N integers.
—— from https://docs.scipy.org/doc/numpy-1.17.0/reference/arrays.html
ndarray是numpy中的多维数组,数组中的元素具有相同的类型,且可以被索引。
如下所示:
>>> import numpy as np
>>> a = np.array([[0,1,2,3],[4,5,6,7],[8,9,10,11]])
>>> a
array([[ 0, 1, 2, 3],
[ 4, 5, 6, 7],
[ 8, 9, 10, 11]])
>>> type(a)
<class 'numpy.ndarray'>
>>> a.dtype
dtype('int32')
>>> a[1,2]
6
>>> a[:,1:3]
array([[ 1, 2],
[ 5, 6],
[ 9, 10]])
>>> a.ndim
2
>>> a.shape
(3, 4)
>>> a.strides
(16, 4)
注:np.array
并不是类,而是用于创建np.ndarray
对象的其中一个函数,numpy中多维数组的类为np.ndarray
。
ndarray的设计哲学
ndarray的设计哲学在于数据存储与其解释方式的分离,或者说copy
和view
的分离,让尽可能多的操作发生在解释方式上(view
上),而尽量少地操作实际存储数据的内存区域。
如下所示,像reshape
操作返回的新对象b
,a
和b
的shape
不同,但是两者共享同一个数据block,c=b.T
,c
是b
的转置,但两者仍共享同一个数据block,数据并没有发生变化,发生变化的只是数据的解释方式。
>>> a
array([[ 0, 1, 2, 3],
[ 4, 5, 6, 7],
[ 8, 9, 10, 11]])
>>> b = a.reshape(4, 3)
>>> b
array([[ 0, 1, 2],
[ 3, 4, 5],
[ 6, 7, 8],
[ 9, 10, 11]])
# reshape操作产生的是view视图,只是对数据的解释方式发生变化,数据物理地址相同
>>> a.ctypes.data
80831392
>>> b.ctypes.data
80831392
>>> id(a) == id(b)
false
# 数据在内存中连续存储
>>> from ctypes import string_at
>>> string_at(b.ctypes.data, b.nbytes).hex()
'000000000100000002000000030000000400000005000000060000000700000008000000090000000a0000000b000000'
# b的转置c,c仍共享相同的数据block,只改变了数据的解释方式,“以列优先的方式解释行优先的存储”
>>> c = b.T
>>> c
array([[ 0, 3, 6, 9],
[ 1, 4, 7, 10],
[ 2, 4, 8, 11]])
>>> c.ctypes.data
80831392
>>> string_at(c.ctypes.data, c.nbytes).hex()
'000000000100000002000000030000000400000005000000060000000700000008000000090000000a0000000b000000'
>>> a
array([[ 0, 1, 2, 3],
[ 4, 5, 6, 7],
[ 8, 9, 10, 11]])
# copy会复制一份新的数据,其物理地址位于不同的区域
>>> c = b.copy()
>>> c
array([[ 0, 1, 2],
[ 3, 4, 5],
[ 6, 7, 8],
[ 9, 10, 11]])
>>> c.ctypes.data
80831456
>>> string_at(c.ctypes.data, c.nbytes).hex()
'000000000100000002000000030000000400000005000000060000000700000008000000090000000a0000000b000000'
# slice操作产生的也是view视图,仍指向原来数据block中的物理地址
>>> d = b[1:3, :]
>>> d
array([[3, 4, 5],
[6, 7, 8]])
>>> d.ctypes.data
80831404
>>> print('data buff address from {0} to {1}'.format(b.ctypes.data, b.ctypes.data + b.nbytes))
data buff address from 80831392 to 80831440
副本是一个数据的完整的拷贝,如果我们对副本进行修改,它不会影响到原始数据,物理内存不在同一位置。
视图是数据的一个别称或引用,通过该别称或引用亦便可访问、操作原有数据,但原有数据不会产生拷贝。如果我们对视图进行修改,它会影响到原始数据,物理内存在同一位置。
视图一般发生在:
- 1、numpy 的切片操作返回原数据的视图。
- 2、调用 ndarray 的 view() 函数产生一个视图。
副本一般发生在:
- Python 序列的切片操作,调用deepCopy()函数。
- 调用 ndarray 的 copy() 函数产生一个副本。
—— from NumPy 副本和视图
view
机制的好处显而易见,省内存,同时速度快。
ndarray的内存布局
NumPy arrays consist of two major components, the raw array data (from now on, referred to as the data buffer), and the information about the raw array data. The data buffer is typically what people think of as arrays in C or Fortran, a contiguous (and fixed) block of memory containing fixed sized data items. NumPy also contains a significant set of data that describes how to interpret the data in the data buffer.
—— from NumPy internals
ndarray的内存布局示意图如下:
可大致划分成2部分——对应设计哲学中的数据部分和解释方式:
- raw array data:为一个连续的memory block,存储着原始数据,类似C或Fortran中的数组,连续存储
- metadata:是对上面内存块的解释方式
metadata都包含哪些信息呢?
dtype
:数据类型,指示了每个数据占用多少个字节,这几个字节怎么解释,比如int32
、float32
等;ndim
:有多少维;shape
:每维上的数量;strides
:维间距,即到达当前维下一个相邻数据需要前进的字节数,因考虑内存对齐,不一定为每个数据占用字节数的整数倍;
上面4个信息构成了ndarray
的indexing schema,即如何索引到指定位置的数据,以及这个数据该怎么解释。
除此之外的信息还有:字节序(大端小端)、读写权限、C-order(行优先存储) or Fortran-order(列优先存储)等,如下所示,
>>> a.flags
C_CONTIGUOUS : True
F_CONTIGUOUS : False
OWNDATA : True
WRITEABLE : True
ALIGNED : True
WRITEBACKIFCOPY : False
UPDATEIFCOPY : False
ndarray
的底层是C和Fortran实现,上面的属性可以在其源码中找到对应,具体可见PyArrayObject和PyArray_Descr等结构体。
为什么可以这样设计
为什么ndarray
可以这样设计?
因为ndarray
是为矩阵运算服务的,ndarray
中的所有数据都是同一种类型,比如int32
、float64
等,每个数据占用的字节数相同、解释方式也相同,所以可以稠密地排列在一起,在取出时根据dtype
现copy一份数据组装成scalar
对象输出。这样极大地节省了空间,scalar
对象中除了数据之外的域没必要重复存储,同时因为连续内存的原因,可以按秩访问,速度也要快得多。
>>> a
array([[ 0, 1, 2, 3],
[ 4, 5, 6, 7],
[ 8, 9, 10, 11]])
>>> a[1,1]
5
>>> i,j = a[1,1], a[1,1]
# i和j为不同的对象,访问一次就“组装一个”对象
>>> id(i)
102575536
>>> id(j)
102575584
>>> a[1,1] = 4
>>> i
5
>>> j
5
>>> a
array([[ 0, 1, 2, 3],
[ 4, 4, 6, 7],
[ 8, 9, 10, 11]])
# isinstance(val, np.generic) will return True if val is an array scalar object. Alternatively, what kind of array scalar is present can be determined using other members of the data type hierarchy.
>> isinstance(i, np.generic)
True
这里,可以将ndarray
与python中的list
对比一下,list
可以容纳不同类型的对象,像string
、int
、tuple
等都可以放在一个list
里,所以list
中存放的是对象的引用,再通过引用找到具体的对象,这些对象所在的物理地址并不是连续的,如下所示
所以相对ndarray
,list
访问到数据需要多跳转1次,list
只能做到对对象引用的按秩访问,对具体的数据并不是按秩访问,所以效率上ndarray
比list
要快得多,空间上,因为ndarray
只把数据紧密存储,而list
需要把每个对象的所有域值都存下来,所以ndarray
比list
要更省空间。
小结
下面小结一下:
ndarray
的设计哲学在于数据与其解释方式的分离,让绝大部分多维数组操作只发生在解释方式上;ndarray
中的数据在物理内存上连续存储,在读取时根据dtype
现组装成对象输出,可以按秩访问,效率高省空间;- 之所以能这样实现,在于
ndarray
是为矩阵运算服务的,所有数据单元都是同种类型。
参考
- Array objects
- NumPy internals
- NumPy C Code Explanations
- Python Types and C-Structures
- How is the memory allocated for numpy arrays in python?
- NumPy 副本和视图
理解numpy中ndarray的内存布局和设计哲学的更多相关文章
- DirectX11--HLSL中矩阵的内存布局和mul函数探讨
前言 说实话,我感觉这是一个大坑,不知道为什么要设计成这样混乱的形式. 在我用的时候,以row_major矩阵,并且mul函数以向量左乘矩阵的形式来绘制时的确能够正常显示,并不会有什么感觉.但是也有人 ...
- 深入理解Java虚拟机之JVM内存布局篇
内存布局**** JVM内存布局规定了Java在运行过程中内存申请.分配.管理的策略,保证了JVM的稳定高效运行.不同的JVM对于内存的划分方式和管理机制存在部分差异.结合JVM虚拟机规范,一起来 ...
- JVM中对象的内存布局与访问定位
一.对象的内存布局 已主流的HotSpot虚拟机来说, 在HotSpot虚拟机中,对象在内存中存储的布局可以分为3块区域:对象头(Header).实例数据(Instance Data)和对齐填 ...
- STL库中string类内存布局的探究
在STL中有着一个类就是string类,他的内存布局和存储机制究竟是怎么样的呢? 这就是建立好的string 可以看出,图中用黄色框框标注的部分就是主要区域 我们用来给string对象进行初始化的字符 ...
- 99.9%的Java程序员都说不清的问题:JVM中的对象内存布局?
本文转载自公众号:石彬的架构笔记,阅读大约需要8分钟. 作者:李瑞杰 目前就职于阿里巴巴,资深 JVM 研究人员 在 Java 程序中,我们拥有多种新建对象的方式.除了最为常见的 new 语句之外,我 ...
- 深入理解JVM(三) -- 对象的内存布局和访问定位
一 对象的内存布局: 在HotSpot虚拟机中,对象在内存中存储的布局可以分为3块区域:对象头(Header),实例数据(Instance Data)和对齐填充(Padding). HotSpot的对 ...
- C# Struct的内存布局
转载:http://www.csharpwin.com/csharpspace/10454r4891.shtml 问题:请说出以下struct的实例大小以及内存布局 struct Struct1 { ...
- C++对象的内存布局以及虚函数表和虚基表
C++对象的内存布局以及虚函数表和虚基表 本文为整理文章, 参考: http://blog.csdn.net/haoel/article/details/3081328 http://blog.csd ...
- 基础篇:JVM运行时内存布局
目录 1 JVM的内存区域布局 2 JVM五大数据区域介绍 3 JVM运行时内存布局和JMM内存模型区别 4 JMM内存模型交互操作 欢迎指正文中错误 关注公众号,一起交流 参考文章 1 JVM的内存 ...
随机推荐
- 洛谷$P2570\ [ZJOI2010]$贪吃的老鼠 网络流+二分
正解:网络流+二分 解题报告: 传送门$QwQ$ 和上一题有点儿像,,,?$QwQ$但是比上一题要有趣很多$QwQ$ 首先把大致思路捋下?依然是.二分出每个奶酪的开始和结束时间,然后check下最大流 ...
- Linux 学习笔记 2 Centos 安装与网络的配置以及VI编辑器的使用
前言 当然,还是觉得Centos 在众多的Linux 发行版中,还是很有地位的,好多的服务器大多沿用的都是一代的Centos 因为它开源(这是废话)而且稳定,这才是服务器沿用的最重要的一项指标. 镜像 ...
- SpringBoot系列之集成Dubbo示例教程
一.分布式基本理论 1.1.分布式基本定义 <分布式系统原理与范型>定义: "分布式系统是若干独立计算机的集合,这些计算机对于用户来说就像单个相关系统" 分布式系统(d ...
- MySQL基础篇(04):存储过程和视图,用法和特性详解
本文源码:GitHub·点这里 || GitEE·点这里 一.存储过程 1.概念简介 存储程序是被存储在服务器中的组合SQL语句,经编译创建并保存在数据库中,用户可通过存储过程的名字调用执行.存储过程 ...
- 让你彻底明白TCP三次握手,四次挥手
今天我们来讲一下TCP的三次握手和四次挥手,先来张思维导图. 一.TCP是什么 TCP(Transmission Control Protocol 传输控制协议)是一种面向连接的.可靠的.基于字节流 ...
- linux下配置vnc-server 和gnome-session
机器比较老,安装时间也十分久远,所以也不知道实验室系统当时是不是完全安装,最近需要使用vnc登录显示界面,结果问题就来了...没有安装vnc-server. (1)机器系统是rhel6.2的,所以就从 ...
- 为什么样本方差的分母是n-1?为什么它又叫做无偏估计?
为什么样本方差的分母是n-1?最简单的原因,是因为因为均值已经用了n个数的平均来做估计在求方差时,只有(n-1)个数和均值信息是不相关的.而你的第n个数已经可以由前(n-1)个数和均值 来唯一确定,实 ...
- 海思dv300cv500交叉编译webrtc
感谢声网提供的webrtc国内源码镜像. 首先要安装好海思编译工具链和git. 先替换一下webrtc代码的仓库网址路径 git config --global user.email "10 ...
- 《工具》centos 7.x 下安装 JDK,超详细总结一看就会
第一步 检查 JDK 查看JDK版本,在命令行输入:java -version [root@test ~]# java -version bash: java: command not found 如 ...
- python之对象回收机制
python中,当程序执行完毕之后,python的垃圾回收机制就会将所有对象回收,清除占用的内存 请看如下代码 class Parent(): def __init__(self,name): sel ...