高级编程技巧——Python 垃圾回收及性能分析

高级编程技巧——Python 垃圾回收及性能分析

Python 垃圾回收及性能分析

3.1 通过实例方法名字的字符串调用方法

  • 我们有三个图形 Circle,Triangle,Rectange
  • 他们都有一个获取面积的方法,但是方法名字不同,我们可以实现一个统一的获取面积的函数,使用每种方法名进行尝试,调用相应的接口。
class Triangle:
    def __init__(self,a,b,c):
        self.a,self.b,self.c=a,b,c
   
    def get_area(self):
        a,b,c= self.a,self.b,self.c
        p=(a+b+c)/2
        return (p*(p-a)*(p-b)*(p-c))**0.5 
class Rectangle:
    def __init__(self,a,b):
        self.a, self.b=a,b
    def getArea(self):
        return self.a *self.b 
class Circle
    def __init__(self,r)
        self.r=r
    def area(self):
        return self.r **2 ** 3.14159
  • getattr(x,‘y’,None)—>等同于 x.y 当中不含有y时,返回None。
  • map(func,iterable)—> 将iterable 中的元素一一映射到func函数中处理,并且返回新的map对象。

3.2 垃圾回收机制

介绍

  • 在Python 程序运行的时候,会在内存中开辟一块空间,用于存放临时变量;当计算完成之后,就会将结果输出到永久存储器中,如果数据量特别大,那内存空间管理部妥当的话就非常容易爆内存,程序可能直接终止。
  • 在Python中,一切皆对象。所以,每一个变量,实际上都是对象的一个指针。所以,当这个对象的引用计数(指针数)为0的时候,说明它也变成了垃圾,需要被放到回收箱中。

OS模块

  • 与操作系统交互的库

psutill模块

  • 与系统交互的库,能够轻松实现获取系统运行的进程和系统利用率(包括CPU、内存、磁盘、网络等)信息,它是用来做系统监控、性能分析、进程管理。
import os
import psutil

def show_info(start):
    # 获取当前进程id
    pid=os.getpid()

    # 获取当前堆成对象
    p=psutil.Process(pid)

    # 获取进程独自占用的物理内存  换算单位MB
    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(1000000)]
    show_info('created')

func()
show_info('finished')

import os
import psutil
def show_info(start):
    # 获取当前进程id
    pid=os.getpid()

    # 获取当前堆成对象
    p=psutil.Process(pid)

    # 获取进程独自占用的物理内存  换算单位MB
    info=p.memory_full_info()

    memory=info.uss/1024./1024
    print(f'{start}一共占用{memory:2f}MB')

def func():
    show_info('initial')
    global a    # a是局部变量,用global 声明它是全局变量
    a=[i for i in range(1000000)]
    show_info('created')
func()
show_info('finished')

import psutil
def show_info(start):
    # 获取当前进程id
    pid=os.getpid()

    # 获取当前堆成对象
    p=psutil.Process(pid)

    # 获取进程独自占用的物理内存  换算单位MB
    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(1000000)]
    show_info('created')
    return a
a=func()
show_info('finished')
  • a是局部变量时,在返回到函数调用处时,局部变量的引用会注销。这时,列表a所指代对象的引用数为0,Python便会执行垃圾回收,因此之前占用的内存被收回了。
  • a是全局变量时,即使函数体内代执行完毕,返回到函数调用处时,对列表a的引用仍然是存在的,所以对象不会被垃圾回收,仍然占用大量内存。

Python 内部的引用计数机制

  • 可以通过sys.getrefcount() 这个函数,来了解**Python内部的引用计数机制。
import sys
a=[1,2,3]
# print(sys.getrefcount(a)) # a这里引用了 2 次

def func(a):
    # 这里调用a 4次
    # a本身一次  函数调用1次  函数参数一次 getrefcount 一次
    print(sys.getrefcount(a))
func(a)

import sys
a=[1,2,3]
def func(a):
    print(sys.getrefcount(a))

func(a)
# a作为形参相当于函数体内的临时变量 所以 调用执行完毕会被释放掉 引用次数为0
# 2 a 本身一次  getrefcount 一次
print(sys.getrefcount(a))

import sys
a=[1,2,3]
print(sys.getrefcount(a))  # 2

b=a
print(sys.getrefcount(a))  # 3

c=b
d=c
print(sys.getrefcount(a))  # 5
# 1.getrefcount() 只计一次运用次数
# 2.变量赋值 b变量指向了 a所在内存地址
  • getrefcount 本身也会引入一次计数

手动启动垃圾回收

  • 如果我们可以在手动删除完对象的引用,然后强调用gc.collect()清除没有引用的对象,其实也就是手动的启动对象的回收。
import sys
import gc

a = [1,2,3]
print(sys.getrefcount(a))

# a=None  # 相当于将a 变量指向了 None

del a  # 相当于自己把对象的引用删掉  本质上对象还没有被删除
gc.collect()  # 手动启动回收
print(a)  # NameError: name 'a' is not defined   此时a 已经被回收

import sys
import gc

a = 1  # 小整数对象池
b=10000
print(sys.getrefcount(a)) # 134
print(sys.getrefcount(b)) # 4

循环引用

  • 如果有两个对象,他们互相引用,并且不再被别的对象引用,那么它们应该被垃圾回收吗?
import os
import psutil
import gc
'''
引用次数 为0 的时候 一定会启用 垃圾回收吗
垃圾回收 一定是引用次数 为0的时候吗
充分必要条件
'''
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=[i for i in range(1000000)]
    b=[i for i in range(1000000)]
    show_info('created')

    # 相互引用
    a.append(b)
    b.append(a)

a=func()
gc.collect()  # 手动回收  如果引用次数不为0时  手动回收  也是可以的
show_info('finished')
  • 总而言之,当双向引用的时候,引用计数虽然还在,但我们可以手动拉起来回收,进行释放内存,所以,引用次数是垃圾回收的充分非必要条件

调试内存泄漏

  • 在Python 中通过引用计数和垃圾回收来管理内存,但是在一定情况下也会产生内存泄露
    • 第一是对象被另一个生命周期特别长得对象所引用
    • 第二是循环引用中的对象定义了 __del __函数

objgraph,一个非常好用的可视化引用关系的包。在这个包中的 show_refs(),它可以清晰的引用关系图。

import objgraph
a=[1,2,3]
b=[4,5,6]
a.append(b)
b.append(a)
objgraph.show_refs(a)
  • .dot 文件转图片:https://onlineconvertfree.com/ 在这里插入图片描述

3.3 用 pdb 进行代码调试

链接
链接
打印大法
Debug
pdb

如何使用pdb

  • 首先,要启动pdb调试,我们只需要在程序中,加入 import pdb和pdb.set_trace() 这两行代码就行。
a=1
b=2
import pdb
pdb.set_trace
c=3
print(a+b+c)
  • 这时,我们就可以执行,在IDE 断点调用器中可以执行的一切操作,比如打印,语法是’p’:
(pdb) p a
1
(pdb) p b
2
  • 除了打印,常见的操作还有 ‘n’ ,表示继续执行代码到下一行
(pdb)n
-->print(a+b+c)
  • 而命令!,则表示列举出当前代码行上下的11行源代码,方便开发者熟悉当前断点周围的代码状态
(pdb)1
  a=1
  b=2
  import pdb
  pdb.set_trace()
  -> c=3
  print(a+b+c)
  • 命令 ‘s’ ,就是step into 的意思,即进入相应对应的代码内部
  • 当然,除了这些常用命令,还有许多其它的命令可以调用
  • 参考对应的官方文档:https//docs.python.org/3/library/pdb.htm|#module-pdb)

3.4 用cProfile 进行性能分析

  • 除了要对程序进行调试,性能分析也是每个开发者的必备技能。
  • 日常工作中,我们常常会遇到这样的问题:在线上,我发现产品的某个功能模块效率低下,延迟高,占用资源多,但却不知道是哪里出了问题。这时,对代码进行profile 就显得异常重要了。
  • 这里所谓的profile ,是指对代码的每个部分进行动态的分析,比如准确计算出每个模块消耗的时间等。
  • 计算斐波拉契数列,运用递归思想
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)

# 接下来 我想要测试一下这段代码总的效率以及各个部分的效率

import cProfile
cProfile.run('fib_seq(30)')   # fib_seq(30) 必须为字符串

在这里插入图片描述
参数介绍

  • ncalls: 函数调用的次数,如果这一有两个值,就表示有递归调用,第二个值是原生调用次数,第一个值是总调用次数。
  • tottime: 函数内部消耗的总时间。(可以帮助优化)
  • percall: 是tottime 除以ncalls,一个函数每次调用平均消耗时间
  • cumtime:之前所有子函数消费时间的累积和。
  • filename: lineno(function): 被分析函数所在文件名,行号,函数名。

3.5 经典的参数错误

def add(a,d):
    a+=b
    return a
a=1
b=2
c=add(a,b)
print(c)  # 3
print(a,b)  # 1 2

def add(a,d):
    a+=b
    return a

a=[1,2]
b=[3,4]
c=add(a,b)
print(c)  # [1, 2, 3, 4]
print(a,b) # [1, 2, 3, 4] [3, 4]  可变的数据类型

注意

  • 列表为可变类型
    • li+=1 相当于改变li本身
    • li =li+1 相当于li 是两个变量 id 不一致 返回的是 原本的li
  • 元组为不可变类型
  • tu +=1 也就是重新创建了一个 tu 变量 id 不一致。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值