python-面向运行时性能优化

本文深入探讨了Python的运行机制,包括语言运行过程、内存管理(垃圾回收算法如引用计数、标记清除、分代回收)以及全局解释器锁(GIL)。此外,还介绍了Python的性能优化,包括性能瓶颈分析工具如timeit、cProfile、line_profiler和py-spy。在基础数据结构对比中,分析了list、tuple、dict和set的时间复杂度。最后,讨论了Python的并行编程,涉及线程、进程和协程,并比较了它们的优缺点。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

python-面向运行时性能优化

一: python运行机制

1> 语言运行过程

1. 高级语言分类及特点

A: 分类

高级语言-程序的执行方式 分类:编译型语言、解释型语言;

分类 含义
编译型语言 将所有源代码一次性转换成二进制指令,也就是生成一个可执行程序(如Windows 下的 .exe),比如C语言、C++、Golang、Pascal(Delphi)、汇编等;
解释型语言 一边执行一边转换,需要哪些源代码就转换哪些源代码,不生成可执行程序,比如 Python、JavaScript、PHP、Shell、MATLAB 等
半编译半解释型语言 既用了解释器也用编译器,如 Java 和 C## 。源代码需要先转换成一种中间文件(字节码文件),然后再将中间文件拿到虚拟机中执行

使用的转换工具称为 :编译器
编译器的类型:前端编译器、后端编译器

编译器分类 含义
前端编译器 编译器的分析阶段也称为前端,它将程序划分为基本的组成部分,检查代码的语法、语义和语法,然后生成中间代码。分析阶段包括词法分析、语义分析和语法分析
后端编译器 编译器的合成阶段也称为后端,优化中间代码,生成目标代码。合成阶段包括代码优化器和代码生成器
B: 特点
编译型语言 特点
1 编译型语言经过编译器转成可执行程序后,可执行程序里面包含的就是机器码。只要拥有可执行程序,可以随时运行,不用再重新编译,即 一次编译,无限次运行;
2 编译型语言可以脱离开发环境运行;运行可执行程序时,不再需要源代码和编译器
3 编译型语言一般不能跨平台,即不能在不同的操作系统之间随意切换
解释型语言 特点
1 解释型语言,每次执行程序都需要一边转换一边执行
2 无法脱离开发环境,所以解释型语言的执行效率天生就低于编译型语言,甚至存在数量级的差距
3 解释型语言 能跨平台,即实现“一次编写,到处运行
C: 对比
类型 原理 优点 缺点
编译型语言 通过专门的编译器,将所有源代码一次性转换成特定平台(Windows、Linux 等)执行的机器码(以可执行文件的形式存在) 编译一次后,脱离了编译器也可以运行,并且运行效率高 可移植性差,不够灵活
解释型语言 由专门的解释器,根据需要将部分源代码临时转换成特定平台的机器码 跨平台性好,通过不同的解释器,将相同的源代码解释成不同平台下的机器码 一边执行一边转换,效率很低

2. python 运行过程

在这里插入图片描述
理解:

注释 理解
运行过程 C、C++等语言都是:先经过预处理、编译、汇编、链接、最终生成机器代码(可执行文件)。而python每次运行,多了中间的两步(编译、解释)
.pyc文件存在的目的 .pyc是PyCodeObject的一种永久保存方式,对可能重用的模块不用再重新解释,提高程序运行速度

2> python 内存管理机制

python的自动垃圾回收机制,在Python中创建对象时无须手动释放。这对开发者非常友好,让开发者无须关注低层内存管理;

1. 垃圾回收算法

在python中,垃圾回收算法以引用计数为主,标记-清除和分代收集两种机制为辅;

A: 引用计数
原理 理解
1 每个对象有一个整型的引用计数属性。用于记录对象被引用的次数
2 例如对象A,如果有一个对象引用了A,则A的引用计数+1
3 当引用删除时,A的引用计数-1
4 当A的引用计数为0时,即表示对象A不可能再被使用,直接回收

demo
Python中,可以通过sys模块的getrefcount函数获取指定对象的引用计数器的值,

import sys
# People类里有两个成员方法(一个有参数,一个无参数)、一个静态方法
class People:

    def __init__(self):
        self.__age = 20

    def name(self, firstName, lastName):
        return firstName + ' ' + lastName

    def age(self):
        return self.__age

    @staticmethod
    def class_name():
        return People.__name__

print("创建对象 0 + 1 =", sys.getrefcount(People()))
P = People()
print("创建对象 1+ 1 =", sys.getrefcount(P))
A = P
a = A
print("创建对象 2+ 2 =", sys.getrefcount(a))
print("创建对象 2+ 2 =", sys.getrefcount(P))
print("创建对象 2+ 2 =", sys.getrefcount(A))
a = None
print("创建对象 4 -1 =", sys.getrefcount(P))
print("创建对象 4 -1 =", sys.getrefcount(A))
del A
print("创建对象 3 -1 =", sys.getrefcount(P))
list_P = [P, P, P]
print("P 三次加入列表被调用;创建对象 2 + 3 =", sys.getrefcount(P))
========================
创建对象 0 + 1 = 1
创建对象 1+ 1 = 2
创建对象 2+ 2 = 4
创建对象 2+ 2 = 4
创建对象 2+ 2 = 4
创建对象 4 -1 = 3
创建对象 4 -1 = 3
创建对象 3 -1 = 2
P 三次加入列表被调用;创建对象 2 + 3 = 5



优点&缺点

引用计数 含义
优点 1, 高效、逻辑简单,只需根据规则对计数器做加减法;2,实时性。一旦对象的计数器为零,就说明对象永远不可能再被用到,无须等待特定时机,直接释放内存。
缺点 1, 需要为对象分配引用计数空间,增大了内存消耗;2, 当需要释放的对象比较大时,如字典对象,需要对引用的所有对象循环嵌套调用,可能耗时比较长;3, 循环引用。这是引用计数的致命伤,引用计数对此是无解的,因此必须要使用其它的垃圾回收算法对其进行补充
B: 标记清除

标记-清除算法:主要用于潜在的循环引用问题;

序列 算法步骤
1 标记阶段。将所有的对象看成图的节点,根据对象的引用关系构造图结构。从图的根节点遍历所有的对象,所有访问到的对象被打上标记,表明对象是“可达”的;
2 清除阶段。遍历所有对象,如果发现某个对象没有标记为“可达”,则就回收;

demo

class A():
    def __init__(self):
        self.obj = None


def func():
    a = A()
    b = A()
    c = A()
    d = A()

    a.obj = b
    b.obj = a
    return [c, d]


e = func()

上面代码中,a和b相互引用,e引用了c和d。整个引用关系如下图所示:
在这里插入图片描述
如果采用引用计数器算法,那么a和b两个对象将无法被回收。而采用标记清除法,从根节点(即e对象)开始遍历,c、d、e三个对象都会被标记为可达,而a和b无法被标记。因此a和b会被回收;

标记清除流程
在这里插入图片描述
标记清除缺点:执行效率低,且会造成空间碎片问题;

C: 分代回收

分代回收原理:在执行垃圾回收过程中,程序会被暂停,即stop-the-world;
为了减少程序的暂停时间,采用分代回收(Generational Collection)降低垃圾收集耗时

解决标记清除扫描遍历开销大,效率低的问题;
将对象分为初生代、中生代、老生代,设置阈值来扫描遍历的类型,以空间换取时间;

分代回收法则

序列 含义
1 接大部分的对象生命周期短,大部分对象都是朝生夕灭
2 经历越多次数的垃圾收集且活下来的对象,说明该对象越不可能是垃圾,应该越少去收集

分代回收理解

序列 含义
1 对象刚创建时为G0。
2 如果在一轮GC扫描中存活下来,则移至G1,处于G1的对象被扫描次数会减少。
3 如果再次在扫描中活下来,则进入G2,处于G1的对象被扫描次数将会更少

在这里插入图片描述

触发GC时机

当某世代中分配的对象数量与被释放的对象之差达到某个阈值的时,将触发对该代的扫描。当某世代触发扫描时,比该世代年轻的世代也会触发扫描;

import gc
threshold = gc.get_threshold()
print("各世代的阈值:", threshold)

# 设置各世代阈值
gc.set_threshold(800, 20, 20)
D: 内存池

python 内存池机制:为了避免频繁的申请和释放内存,python的内置数据类型,数值、字符串,查看python源码可以看到数值缓存范围为 -5 ~ 257;对于 -5 ~ 257 范围内的数值,创建之后python会把其加入到缓存池中,当再次使用时,则直接从缓存池中返回,而不需要重新申请内存,如果超出了这个范围的数值,则每次都需要申请内存;这样能够减少内存碎片,提升效率;
python内置对象(int,dict,list,str等)都有独立的私有内存池,对象之间的内存池不共享;

内存池分类

分类 理解
大内存 以256Bytes 为界限,大于256Bytes 属于大内存,大内存使用Malloc进行分配,
小内存 以256Bytes 为界限,小于256Bytes 属于小内存,小内存使用内存池进行分配;

内存池分别level
python 的对象管理主要位于Level+1 —> Level+3 层;从内存池申请的内存对象被删除后的内存会被归还到内存池,避免了多次频繁释放,能有效减少内存碎片的产生;

level 理解
Level+1 大于256Bytes 属于大内存,使用Malloc进行分配
Level+2 小于256Bytes 属于小内存,由python 对象分配器(内存池)分配;
Level+3 python内置对象(int,dict,list,str等)都有独立的私有内存池,对象之间的内存池不共享;
E: 缓存池

python 缓存池作用:

作用 理解
1 提升运行效率,对常用对象缓存;
2 小整数对象池,intern,常量池和free_list

在这里插入图片描述

3> python 常用解释器

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

45度看我

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值