在python中,一切皆对象,那么垃圾回收机制,其实就是当对象的引用计数为0,也就是当对象没有被调用的时候会被回收。(弱引用不占用引用计数)
python的回收机制一般是 计数回收为主,当遇到双向调用可使用标记清除和分代回收两种机制。
局部变量结束就会被回收
import os
import psutil
def show_info(start):
# 获取当前进程id
pid = os.getpid()
# 获取当前堆成对象
p = psutil.Process(pid)
# 返回该对象的内存消耗
info = p.memory_full_info()
# 获取进程独自占用的物理内存 换算单位 mb
memory = info.uss/1024/1024
print(f"{start}一共占用了{memory:.2f}MB")
def func():
show_info("initial")
# a是局部变量 局部变量在函数执行完毕 引用就会注销
a = [i for i in range(1000000)]
show_info("created")
func()
show_info("finished")
这里的a是局部变量,所以结束就会被回收。
当a为全局变量的时候 global a
,并没有回收
gc 手动垃圾回收
当出现双向引用到时候,引用计数始终存在,永远不为0,始终占用内存,那么它该不该被回收?这时候就要导入gc机制,手动进行垃圾回收
import gc
def show_info(start):
pid = os.getpid()
p = psutil.Process(pid)
info = p.memory_full_info()
memory = info.uss/1024./1024
print(f"{start}一共占用{memory:.2f}MB")
def func():
show_info("initial")
a = [i for i in range(100000)]
b = [i for i in range(100000)]
show_info("after a,b created")
# 相互引用
a.append(b)
b.append(a)
func()
gc.collect()
show_info("finished")
当双向引用的时候,引用计数虽然还在,但我们可以手动拉起回收,进行释放内存gc.collect()
sys.getrefcount()引用计数机制
导入sys,使用 sys.getrefcount(a)
来获取a的引用次数,
sys.getrefcount本身会引入一次计数,多个sys.getrefcount(a) 只计一次
下图第一个Print输出的是2,一次是a 一次是sys.gerrefcount(a)
第二个Print输出的是4,一次a ,一次fun(a),以及形参调用的fun(a),还有一次sys.getrefcount(a)
第三次的Print输出是2,因为fun(a),这里的a作为形参相当于函数体内的临时变量,所以中间的不计次数,调用执行完毕会释放掉,到最后一行输出的只有两次,a本身一次,getrefcount一次
可视化关系 objgraph
可以通过导入objgraph来生成引用关系图
import objgraph
#可视化引用关系的包 show_Refs 生成引用关系图
a = [1,2,3]
b = [4,5,6]
a.append(b)
b.append(a)
objgraph.show_refs(a)
pdb代码调试
因为debug只能往下执行,只能调试Python,所以在多种语言交互的情况下,调试很不方便,导入pdb,可以更好的调试代码。
import pdb
a = 1
b = 2
pdb.set_trace() # z追踪
c = 3
print(a+b+c)
def func():
print(" i am func")
func()
pdb调试状态下,n:下一条语句,s:进入函数 h:帮助 l:列出附近的代码 p 输出值 q 退出debug
cprofile 性能分析
import cProfile
# 性能调试
def fib(n):
if n == 0:
return 0
elif n == 1:
return 1
else:
return fib(n-1)+fib(n-2)
def fib_seq(n):
res = []
if n>0:
res.extend(fib_seq(n-1))
res.append(fib(n))
return res
fib_seq(30)
cProfile.run('fib_seq(30)')
第一行:7049218个函数调用被监控,其中96个为原生调用
calls:被调用的次数,如果是两个值,表示有递归调用,第一个是总调用次数,第二个是原生调用次数
tottime:函数内部消耗的总时间,可以帮助优化
percall:tottime/calls 表示一个函数每次调用平均消耗时间
cumtime:之前所有子函数消费时间的累计和
filename:lineno:被分析函数所在文件名,所在的行号,函数名