1.内存分析和处理
程序的运行离不开对内存的操作,一个软件要运行,需要将数据加载到内存中,通过CPU进行内存数据的读写,完成数据的运算
1.1程序内存浅析
软件程序在计算机中的执行,主要通过数据单元、控制单元、执行单元共同协作,完成数据的交互,达到程序处理数据的目的,在软件执行的过程中,由于系统内存和CPU的资源非常有限,所以有效的分解软件中的各项数据,将不同的数据加载到不同的内部部分以及有效的运行程序,同时可以达到在一个计算机中有效运行更多软件的目的
python程序在运行过程中,主要是解释器从系统中申请的内存空间以运行python软件解释器将申请的内存主要区分为这样几个部分用于处理执行的软件程序
python程序在运行过程中,主要是解释器从系统中申请的内存空间以运行python软件解释器将申请的内存主要区分为这样几个部分用于处理执行的软件程序
|
---|
栈内存区[stack]:用于直接分配数据,存取速度较快、数据存储不稳定,适用于小数据块的快速存取,一般在程序中用于存储数据变量 |
方法区[data]:主要用于加载程序中使用的代码数据、二进制数据、方法数据等等程序运行需要的预加载数据 |
静态区[static]:主要用于加载存储程序中的一些静态数据、常量数据等等,在python中的不可变数据类型的数据也会存储在静态常量区内存中 |
堆内存区[heap]:存储数据稳定持久,一般用于存储加载较为重量级的数据,如程序运行过程中的对象都是存在堆内存中 |
程序中变量和对象的基本表示方式
程序中有一个类型hero,在代码中创建一个Hero对象,将对象赋值给一个变量hero,内存中内存信息的分配如下:
1.1.1不可变数据类型 VS 可变数据类型
python中根据数据是否可以进行修改提供了两种不同的数据类型
1)不可变数据类型:一般基本数据类型都是不可变数据类型
2)可变数据类型:一般组合数据类型或者自定义数据类都是可变数据类型
怎么区分可变和不可变?为什么要有这样的规则?
python中的一切都是对象,可以通过id函数查询对象在内存中的地址数据,可变数据类型是在定义了数据之后,修改变量的数据,内存地址不会发生变化
不可变数据类型是在定义了数据之后,修改变量的数据,变量不会修改原来内存地址中的数据,而是会指向新的地址,原有的数据保留,这样更加方便程序中基本数据的利用率
-|
|
从内存的角度分析代码:
def chg_data_1(x):
x = 12
print("method: {}".format(x))
def chg_data_2(y):
y.append("hello")
print("method: {}".format(y))
if __name__ == "__main__":
a = 10
chg_data_1(a)# 实际参数传递不可变类型
print("main:", a) # 这里 a 是多少?
b = [1,2,3]
chg_data_2(b)# 实际参数传递可变类型
print("main:", b) # 这里的 b 是多少?
1.1.2
python中的最小运行单元是代码块,代码块的最小单元是一行代码
在实际开发过程中,需要注意的是python有两种操作方式:
1)交互模式
2)IDE开发模式
在交互模式下,每行命令是一个独立运行的代码块,每个代码块运行会独立申请一次内存,在操作过程中交互模式没有推出的情况下遵循python官方操作标准
但在IDE开发模式下,代码封装在模块中,通过python命令运行模块时,模块整体作为一个代码块向系统申请内存并执行程序,执行过程中对于基本数据类型进行缓存优化操作
1.2程序内存代码检测
python对于内存的操作,社区开发了一款比较强大的专门用于检测代码内存使用率,用于项目代码调优的模块memory_profile是一个使用较为简单,并且可视化比较直观的工具模块,通过pip工具安装即可使用
-|
pip install memory_profiler|
通过测试的函数或者类型前面添加@profile 注解,让内存分析模块可以直接进行代码运行监测
from memory_profiler import profile
@profile
def chg_data_1(x):
x = 12
print("method: {}".format(x))
@profile
def chg_data_2(y):
y.append("hello")
print("method: {}".format(y))
if __name__ == "__main__":
a = 10
chg_data_1(a)# 实际参数传递不可变类型
print("main:", a) # 这里 a 是多少?
b = [1,2,3]
chg_data_2(b)# 实际参数传递可变类型
print("main:", b) # 这里的 b 是多少?
执行代码程序的结果如下
1.3操作符号: is 和==的使用
python提供了对象操作符号is和内容操作符号==,用于判断对象和对象中的值的情况
A is B:判断对象A 和对象B 是否同一个内存地址,即是否同一个对象
A == B:判断A中的内容是否和B 中的内容一致
不论是基本类型的数据,还是内容复杂的对象,都可以通过对象判断符号is 和内容判断操作符号==来进行确定
不可变数据类型的数据判断:
TODO
组合数据类型的数据判断
创建的每个组合数据类型的对象都是独立的额TODO
引用数据类型的操作:自定义数据类型,变量中存放的是对象在内存中的地址
自定义类型的对象,每次创建同样也是在堆内存中单独创建的对象,所以is判断Flase,创建的对象在通过==判断时,会自动调用父类的eq()函数进行判断,默认情况下创建的对象内部的变量,使用的是对象自己的内存空间,所以==判断的是Flase
1.4引用、浅拷贝、深拷贝
1.4.1对象的内存分配
对象的创建,依赖于申请内存空间中数据的加载,对象在内存的创建过程依赖于三部分内存处理:对象分配内存地址、引用变量分配内存地址、对象和引用变量之间的关联。
由于对象的创建,是将堆内存中创建的独象的地址临时存储在栈内存的变量中,python中对于这样的情况,有三种不同的操作方式:
1)如果程序中多个不同的地方都要使用同一个对象,通过对象的引用赋值,将同一个对象赋值给多个变量
2)如果程序中多个不同地方都要使用相同的对象数据,通过对象的拷贝完成数据的简单赋值即可,对象中的包含的数据要求必须统一
3)如果程序中多个不同的地方使用相同的而且独立的对象数据,通过数据的深层次的复制将对象的数据完整复制成独立的另一份即可
1.4.2
对象的引用赋值,可以将对象的内存地址同时赋值给多个变量,这多个变量中存放的都是同一个对象的引用地址如果通过一个变量修改了对象内容,那么其他变量指向的对象内容也会同步发生变化
将一个变量中存放的对象的地址数据,赋值给其他变量,通过赋值操作符号可以完成
对象的引用变量赋值,并不是对象的赋值或者备份,而仅仅是将对象的地址存储在多个变量中方便程序操作
1.4.3对象的浅拷贝
对于程序中对象的拷贝操作,除了引用赋值操作可以完成同一个对象在程序不同位置的操作,这里的浅拷贝更是一种对象的临时备份,浅拷贝的核心机制主要是赋值对象内部数据的引用
1.4.4对象的深拷贝
和对象的浅拷贝不同,对象的深拷贝,是对象数据的直接拷贝,而不是简单的引用拷贝,主要是通过python内建标准模块copy提供的deepcopy函数完成对象深拷贝
1.5垃圾回收机制
垃圾回收机制基本上是所有高级语言的标准配置之一,在一定程度上,能优化编程语言的数据处理效率和提高编程软件开发软件的安全性
在python中的垃圾回收机制主要是以引用计数为主要手段
以标记清除和隔代回收机制作为辅助操作手段
完成对内存中无效数据的自动管理的操作
1.5.1引用计数
引用计数是python中的垃圾回收机制的核心操作算法,该算法最早是George E.Collins在1960年首次提出的,并在大部分高级语言中沿用至今,是很多高级语言的垃圾回收机制核心之一
1)什么是引用计数
引用计数算法的核心思想是:当一个对象被创建或者被拷贝时,引用计数就会+1,当这个对象的多个引用变量,被销毁一个时该对象的引用计数就会-1,如果一个对象的引用计数为0则表示该对象已经不被引用,就可以让垃圾回收机制进行清除并释放该对象占有的内存空间
引用计数算法的优点是:操作简单,实时性能优秀。能在最短的时间获得并运算对象引用数
引用计数算法的缺点是:为了维护每个对象的引用计数操作算法,python必须提供和对象对等的内存消耗来维护引用计数,这样无形中就增加了内存负担,同时引用计数对于循环
应用/对象之间的互相引用,是无法进行引用计数操作的,所以就会造成常驻内存的情况
2)python中的引用计数
python是一个面向对象的弱类型语言,所有的对象都是直接或者间接继承自object类型,object类型的核心其实就是一个结构体对象
typedef struct_object {
int ob_refcnt;
struct_typeobject *ob_type;
} PyObject;
在这个结构体中,ob_refcnt 就是对象的引用计数,当对象被创建或者拷贝时该计数就会
增加+1,当对象的引用变量被删除时,该计数就会减少-1,当引用计数为 0 时,对象数据
就会被回收释放了。在 python 中,可以通过 sys.getrefcount()来获取一个对象的引
用计数
1.5.2标记清除
python中的标记清除机制主要是针对可能产生循环引用的对象进行的检测机制
标记清除算法核心思想:首先找到 PYTHON 中的一批根节点对象,如 object 对象,通过根节点对象可以找到他们指向的子节点对象,如果搜索过程中有这个指向是从上往下的指向,表示这个对象是可达的,否则该对象是不可达的,可达部分的对象在程序中需要保留下来,不可达部分的对象在程序中是不需要保留的
1.5.3分代回收
PYTHON 中的分代回收机制,是一种通过空间换取时间效率的做法,PYTHON 内部处理机制
定义了三个不同的链表数据结构[第零代(年轻代),第 1 代(中年代),第 2 代(老年代)]
PYTHON 为了提高程序执行效率,将垃圾回收机制进行了阈值限定,0 代链表中的垃圾回收
机制执行最为密集,其次是 1 代,最后是 2 代;
1.5.4垃圾回收处理
PYTHON 中的垃圾回收机制有了一定的了解之后,我们针对垃圾回收机制的操作通过代码进
行测试
PYTHON 中的 gc 模块提供了垃圾回收处理的各项功能机制,必须 import gc 才能使用
gc.set_debug(flags):设置 gc 的 debug 日志,一般为 gc.DEBUG_LEAK
gc.collect([generation]):显式进行垃圾回收处理,可以输入参数~参数表示回收的
对象代数,0 表示只检查第 0 代对象,1 表示检查第 0、1 代对象,2 表示检查 0、1、2 代
独对象,如果不传递参数,执行 FULL COLLECT,也就是默认传递 2
gc.set_threshold(threshold0 [, threshold2 [, threshold3]]):设置执行
垃圾回收机制的频率
gc.get_count():获取程序对象引用的计数器
gc.get_threshold():获取程序自动执行 GC 的引用计数阈值
以上是 PYTHON 中垃圾回收机制的基本操作,在程序开发过程中需要注意:
1)项目代码中尽量避免循环引用
2)引入 gc 模块,启用 gc 模块自动清理循环引用对象的机制
3) 将需要长期使用的对象集中管理,减少 GC 资源消耗
4)gc 模块处理不了重写del方法导致的循环引用,如果一定要添加该方法,需要显
式调用 gc 模块的 garbage 中对象的del方法进行处理