编者按:在刚刚完结的 PyCon China 2022 大会上,龙蜥社区开发者严懿宸分享了主题为《Python 启动减速的摸索与实际》的技术演讲。本次演讲,作者将从 CPython 社区相干工作、本计划的设计及实现,以及业务层面的集成等方面进行介绍。
以下为本次演讲内容:
一、Python 启动速度简析
首先从一个 Python 3 中空解释器启动工夫的坏事剖析开始。咱们能够看到,次要的耗时都和 Python 包加载无关。
其中,CPU 工夫中包加载占据了 30% 左右的工夫;而 37% 的等待时间中,磁盘 IO 等破费的工夫也和包加载有较大的关联。
相熟 Python 机制的敌人大略晓得,Python 中加载一个包首先会搜寻对应的 pyc 文件,这是一种序列化的字节码格局。找到之后会对其进行反序列化,并执行其中的代码。如对应的 pyc 文件不存在,会从新编译 py 文件失去字节码,并序列化为 pyc 文件长久化保留。咱们优化的次要指标次要集中在加载包这个过程,心愿可能至多免去每次查找、读取、反序列化的开销。
以 Python3.10 为例,这里是应用 python 解释器启动一个空语句的所需工夫,同时应用了 -Ximporttime 打印出过程中加载每一个包的耗时。能够粗略地看到,包加载工夫大概占了总工夫的 30% 左右。咱们发现这种状况和 Java 虚拟机相似。在 Java 中,Java 会首先将 Java 源代码编译为 Java 字节码,随后由 Java 命令执行。
咱们晓得 Java 的劣势并不包含启动速度,这种流程也是起因之一。那么 Java 如何局部解决这个问题呢?
二、PyCDS(代码对象共享)设计与实现
Java 中有一个叫做 CDS/AppCDS 的机制,通过将 Java 字节码和一些辅助数据长久化保留,在后续启动时应用 mmap 加载,节约了磁盘 IO 和解析验证 class 文件的开销。
很天然的想法是,如果咱们心愿在 Python 中应用相似的技术,指标应该是 Python 字节码。
Python 默认从 py 文件导入模块的逻辑如上图右边所示,首先依据制订的名字获取对应的规定,随后尝试寻找 pyc 文件或从新编译。最初,应用 exec 命令利用代码和一个空 dict 来创立模块,并退出 runtime。
咱们做的事件能够简化为右侧逻辑。同样依据包名,尝试从 mmap 中加载。如果胜利,那么同样的 codeobject 也能够用于初始化。
这样做有什么间接的阻碍?
能够看到,Python 中代码对象的 C 数据结构大抵如图,包含 consts、string、bytes 等 Python 数据类型。
以应用到的 codeobject 作为 root,将波及的数据序列化存储到内存映射中。
在这一步,最间接的问题是内存随机化机制。在解决 code object 中的 Python 对象时,每个 Python 对象头中都保留着指向以后过程中对应类型信息的指针。Runtime 通过这个指针判断该对象在 Python 中的类型。
以 PyCode_Type 为例,如果不做解决,这里会失落类型信息(红色 offset)。
为了解决这个问题,在咱们创立的镜像文件中会保留波及的对象指针。在加载时动静 patch 相干的指针。
在整个过程中波及的 Python 类型包含:
- 常量(bool/None/ellipsis)
- 字面量(float/complex)
- 须要额定调配的变量(long/bytes/str)
- container(tuple/frozenset)
对于常量和字面量,在内存映射中调配好空间后间接赋值即可保留;对于后两种,须要模仿 Python 中变量初始化的逻辑,创立适合的内存大小并写入对应地位。同时,对于十分量的类型,还须要对内存映射中的援用计数额定赋值,避免意外触发 Python 中的回收。
以上就是本我的项目的大抵内容,另外对于我的项目的具体用法请返回 PyCDS 我的项目主页或咱们在龙蜥实验室上的课程查看,链接见下:
龙蜥实验室课程:https://lab.openanolis.cn/#/a…
PyCDS 主页:https://github.com/alibaba/co…
附:PyCon China 2022 大会相干文章浏览:
解读最佳实际:倚天 710 ARM 芯片的 Python+AI 算力优化
—— 完 ——