2.内存分析和处理
程序的运行离不开对内存的操作,一个软件要运行,须将数据加载到内存中,通过CPU进行内存数据的读写,完
成数据的运算。
2.1 程序内存浅析
软件的程序在计算机中的执行,主要是通过数据单元、控制单元、执行单元共同协作,完成数据的交互达到程序处理数据的目的,在软件的执行过程中,由于系统内存和CPU的资源有限,所以有效的分解软件中的各项数据,将不同的数据加载到不同的内存部分以有效的运行程序,同时可以达到在一个计算机中有效运行更多软件的目的。
栈内存区【stack】:用于直接分配数据,存取数度较快,数据存储不稳定,适用于小数据块的快速存取,一般在程序中用于存储变量数据。
方法区【data】:主要用于加载程序中的代码数据、二进制数据、方法数据等等程序运行需要的预加载数据
静态区【static】:主要用于加载存储程序中的一些静态数据、常量数据等等,在PYTHON中的不可变数据类型的数据也会存储在静态常量区内存中
堆内存【heap】:存储数据稳定持久,一般用于存储加载较为重量级的数据,如程序运行过程中的对象都是存在堆内存中的
2.1.1. 不可变类型VS可变类型
一般的基本数据类型都是不可变类型,一般的组合数据类型或者自定义数据类型都是可变数据类型
python中的一切都是对象,可以通过id()函数查询对象在内存中的地址数据,可变类型是在定义过数据类型之后,修改
变量的数据,内存地址不会发生变化,不可变数据类型是在定义了数据之后,修改变量的数据,变量不会修改原来
内存地址的数据而是会指向新地址,原有的数据保留,这样更加方便程序中基本数据的利用率。
# 思考题:
nums = [12, 13, 15, 18, 20, "hello", ["world", "python"]]
a = 20 # nums[4] 是否同一个内存地址? 是!
print(id(a), id(nums[4]))
b = "hello"# nums[5] 是否同一个内存地址?
print(id(b), id(nums[5]))
c = ["world", "python"] # nums[6] 是否同一个内存地址
print(id(c), id(nums[6]))
print(id(nums[6][0]), id(c[0])) # 两个列表中的world字符串
# 奇怪的语法
if [1][0]:
print("hello")
else:
print("world")
-------参考输出值为-------
2590107763536 2590107763536
2590109412848 2590109412848
2590109414336 2590109135424
2590109412976 2590109412976
hello #list的性质
数据类型
可变类型:对象在内存地址中存储的数据可变
不可变类型:对象在内存地址中存储的数据不能变化
1. 可变类型
a = list() # 堆内存中:存在一个对象 list(),一个变量a指向这个对象
print(id(a)) # 查看对象a的内存
print(a)
a.append("hello")
print(id(a))
print(a)
print("-----------------------")
2. 不可变类型
不可变类型,在同一个代码块中进行多次使用时会将该类型的对象,直接创建在常量区,在任意引用时候的时候直接赋值内存地址因为不可变类型的对象数据不会发生变化,所以内存中存储一份即可!优化程序执行效率一般情况下,可变类型的对象会创建在堆内存中;不可变类型的对象会创建在常量区内存中
整数类型:-5~256:在解释器加载时,已经自动分配了这些数字的内存
超出-5~256范围的整数,在一个代码块中申请一次内存
交互模式:一行命令就是一个代码块
IDE模式~工具开发:一个模块就是一个代码块
2.1.2代码和代码块
python中的最小运行单元是代码块,代码块的最小单元是一行代码,需要注意的是在交互模式下,每行命令都是一个独立的代码块,每个代码块都会独立的申请一次内存。-5 ~ 256之间的数据自动缓存,字符串自动缓存,超出范围的数据重新申请内存
2.2程序内存代码检测
面试题
'''
内存检测模块:memory profiler
pip install memory_profiler
测试内存:
在测试函数前面,添加一个检测注解@profile
'''
from memory_profiler import profile
@profile
def chg_nums(n):
n = 20
def chg_list(l):
l.append(20)
a = 18
chg_nums(a)
b = [1,2]
chg_list(b)
print(a) # ?18 20 18 20
print(b) # ?[1,2] [1,2,20] [1,2,20] [1,2]
2.3.操作符号:is和==的使用
跟对象内存相关的三个操作符号:
- a is b:判断两个变量a/b,他们指向的对象是否同一个对象
- a == b:判断两个变量a/b,他们指向的对象的数据内容是否一致【不做深层判断】
- isinstance(a, b) 判断a对象是否属于b类型
新的问题1:如果两个变量,要指向同一个对象?应该怎么操作
例:a = 1000, b = c = a 就能实现多个变量指向同一个对象。
新的问题2:如果有一个对象,我想复制一个单独的对象,应该怎办?
2.4引用、浅拷贝、深拷贝
2.4.1.对象的浅拷贝
对象的浅拷贝指的是,对对象的一种临时备份,浅拷贝的核心机制是主要对赋值对象的内部数据的引用。浅拷贝复制对象的属性值会随着其他拷贝对象对数据改变而改变
2.4.2.对象的深拷贝
对象的深拷贝:拷贝创建一个新对象,同时拷贝对象属性的数据[而不是引用],
2.5.垃圾回收机制
垃圾回收机制:是现阶段任何高级语言的特性!
针对程序运行过程中,出现的垃圾数据进行自动回收
自动回收无效对象数据,通过垃圾回收算法进行操作
垃圾回收:Garbage Collection : GC
遵循垃圾回收算法
PYTHON中,以 引用计数垃圾回收算法 为主要回收机制!
以 标记-清除 和 分代回收 为辅助回收机制!完成PYTHON程序的垃圾回收操作!
2.5.1.引用计数
引用计数算法的原理:python中所有的对象/类型,都是直接或者间接
继承自object对象;问题:谁是object对象
object对象底层是C的一个结构体
typedef struct_object {
int:ob_refcnt,
struct_typeobject *obj_type
} PyObject;
引用计数回收机制:伟大的机制,但是同时存在很大的问题
对象内聚:对象的循环引用
2.5.2标记清除
循环引用的出现,造成了引用计数算法的重大BUG
出现了第二种辅助垃圾回收算法
- 标记-清除
参考画图
2.5.3分代回收 - 分代回收-对于引用计数和标记回收的优化
三种回收机制共同协作,实现了PYTHON非常高处理效率的垃圾回收机制
#对链接内容进行转载和更新:https://www.jianshu.com/p/2f98dd213f04