这期让我们来跟踪下一个简单py文件的执行。
没奖竞猜:仅仅打印一行文字,C语言层面需要执行多少条Python字节码?
简单!让我用dis(Python标准库模块)反编译下字节码,看一眼。执行命令:
这里细节使用自己编译的python_d.exe解释器(不同版本之间字节码可能有差异),例如Python3.8.19的反编译结果:
下面是我们自己编译器的反编译结果,可以看到因为版本更新,已经出现了变化:
数一下一共是六条字节码,那么让我们稍微修改下CPython源码,让它逐个打印执行的字节码,看看字节码顺序和个数能否对应上。
让我们在Python/ceval.c这个文件中包含自己的头文件,并对vscode2022的解决方案做注释中的配置,以便于链接上我们自己写的库:
这是我们导入的库的源文件,包含1个OpcodeEntry结构体,1个opcodeTable映射表和1个打印函数printOpcode:
opcodeTable映射表有非常多行,我们可以python脚本自动生成.h头文件和.c源文件,你需要用到python标准库opcode,然后将相应其它依赖的标准库头文件和函数手动添加到生成的.h文件和.c文件即可:
现在让我们在switch分支中打个断点,同时前面添加上自己的printOpcode函数:
执行一次,打印出了RESUME字节码:
vocode2022按一次F11进行逐语句调试,让我们看看是不是真的进入了RESUME这个分支:
没错,证明我们的函数是有效的(当然一次成功也可能是偶然,不要在意这里的严谨性)。
但是已经发现和我们前面反编译该文件结果的第一个字节码已经对不上了,让我们多执行几次:
好了,这至少可以猜测解释器在真正执行我们的脚本之前,还做了很多其它事情。让我们把断点去掉,看看能不能找到我们想要的字节码序列(脚本文件的):
这里不要头铁再一次一次点了,让我们鲁莽的加个全局变量(这里加局部变量是有问题的哟,毕竟是使用了goto语句的项目,程序的执行路径对我们现在来说是比较迷惑的):
很幸运,我们的脚本字节码序列在python解释器执行的所有字节码的末尾段,在此之后,仅仅多了一个INTERPRETER_EXIT字节码,字节码的执行就退出了(C层面应该还有些其它工作)。
前面反编译的字节码序列和现在真实执行的字节码序列完全一致(掌声:啪啪啪)。
那么为了运行这6条字节码,总共执行了多少条python字节码呢?
要检测这个的话,显然我们需要检测下面这个分支执行了多少次,因为每执行一次字节码,走一次这个switch case。
注意到以下的测试非常不规范,但是可以模仿,自己拉的仓库想怎么造都可以,大不了删了重来。我们定义一个全局变量opcode_count来计数,并实时打印出。
106276条,你猜对了嘛?这条Hello CPython的准备工作真不容易。
(留1坑:执行同一个脚本,字节码总数不一定总是一致。
希望我们下次解决它。)