编者按:在刚刚结束的PyConChina2022大会上,龙蜥社区开发者严奕辰分享了一篇主题为《Python 启动加速的探索与实践》的技术演讲。在本次演讲中,笔者将介绍CPython社区的相关工作,本方案的设计与实现,以及业务层面的整合。以下为本次演讲内容:1.Python启动速度简析。首先,先好好分析一下Python3空心解释器的启动时间。我们可以看到,主要耗时与Python包的加载有关。其中,包加载占用CPU时间的30%左右;而37%的等待时间,花在磁盘IO上的时间也与包加载密切相关。熟悉Python机制的朋友大概都知道,Python在加载一个包的时候,会先去寻找对应的pyc文件,这是一种序列化的字节码格式。一旦找到,就会反序列化,执行里面的代码。如果对应的pyc文件不存在,则重新编译py文件获取字节码,并序列化为pyc文件进行持久化存储。我们优化的主要目标主要集中在加载包的过程中,希望至少避免每次搜索、读取和反序列化的开销。以Python3.10为例,这里是使用python解释器启动一个空语句所需要的时间,使用-Ximporttime打印出过程中加载每个包所花费的时间。可以大致看出,包加载时间占总时间的30%左右。我们发现这种情况类似于Java虚拟机。在Java中,Java首先将Java源代码编译成Java字节码,然后由Java命令执行。我们知道Java的强项不包括启动速度,这个流程是原因之一。那么Java是如何部分解决这个问题的呢?2.PyCDS(CodeObjectSharing)的设计与实现Java中有一种叫做CDS/AppCDS的机制,通过持久化保存Java字节码和一些辅助数据,并在后续启动时用mmap加载,来节省磁盘IO和解析的Overheadforvalidatingclass文件。自然的想法是,如果我们想在Python中使用类似的技术,目标应该是Python字节码。Python默认从py文件导入模块的逻辑如上图左侧所示。首先根据指定的名字得到对应的规则,然后尝试寻找pyc文件或者重新编译。最后,使用exec命令创建包含代码和空字典的模块,并将其添加到运行时。我们所做的可以简化为右手逻辑。同样根据包名,尝试从mmap加载。如果成功,则同样的代码对象也可以用于初始化。这样做的直接障碍是什么?可以看出Python中代码对象的C数据结构大致如图所示,包括consts、string、bytes等Python数据类型。以代码对象为根,将涉及的数据序列化并存储在内存映射中。到这一步,最直接的问题就是内存随机化机制。在处理代码对象中的Python对象时,每个Python对象头都持有一个指向当前进程中对应类型信息的指针。运行时使用这个指针来确定Python中对象的类型。以PyCode_Type为例,如果不处理,这里的类型信息(红色偏移量)会丢失。为了解决这个问题,涉及到的对象指针会保存在我们创建的图像文件中。在加载时动态修补相关指针。整个过程涉及到的Python类型包括:常量(bool/None/ellipsis)字面量(float/complex)需要额外分配的变量(long/bytes/str)常量和字面量的容器(tuple/frozenset),在之后空间是在内存映射中分配的,可以通过直接赋值来节省;对于后两者,需要模拟Python中变量初始化的逻辑,创建合适的内存大小并写入相应的位置。同时,对于非常量类型,需要对内存映射中的引用计数进行额外的赋值,以防止在Python中意外触发回收。以上是本项目的大致内容。另外,项目的具体使用方法请到PyCDS项目主页或者我们在龙蜥实验室的课程。链接如下:DragonLizardLabCourse:https://lab.openanolis.cn/#/a...PyCDS主页:https://github.com/alibaba/co...附件:阅读相关文章PyConChina2022大会:最佳实践解读:倚天710ARM芯片Python+AI算力优化--over--
