当前位置:   article > 正文

Cpython源码分析01_使用Visual Studio2017来研究Cpython,debug和release两种模式下编译的Python中__sizeof__()不一样的地方

cpython源码分析

阅读本文章最好有一些C语言和python语言的基础的读者

1.为什么要研究Cpython

  • 目前python主流的解释器CPython、JPython、IPython、PyPy、IronPython,但是用到最多的、生态最好的还是Cpython。
  • 了解python底存的构造,比如List、Dict、Set、Tuple等他们到底是怎么实现的,而不是用它的时候两眼一抹黑,了解Cpython有助于让我们编写出更加高质量的python代码。
  • 由于Cpython中绝大部分都是用C语言编写的,研究它有助于不会忘记C语言中的基础知识,C语言很重要,不管你现在是那个语言的开发者都应该掌握,至少应该能看懂它。
  • 最重要的是大家肯定不希望自己只是API调用侠吧。

2.研究Cpython用到的工具与环境

  • Visual Studio2017
  • notepad++(辅助)
  • window10 64位
  1. 第一步我们来安装visual studio2017,visual studio2017安装器下载
  2. 双击vs2017下载器,看到如下界面,勾选如下两个工具集,然后选择右下角的下载时安装,然后点击确定即可安装
    在这里插入图片描述
  3. 安装结束后可以看到桌面已经有了Visual Studio2017的图标了
    在这里插入图片描述

3.构建Cpython

Cpython项目目录介绍:
Doc :文档来源
Grammar :计算机可读语言定义
Include :C 头文件
Lib :用 Python 编写的标准库模块
Mac :macOS 支持文件
Misc :杂项文件
Modules :用 C 编写的标准库模块
Objects :核心类型和对象模型
Parser :Python解析器源代码
PC :Windows 为旧版本的 Windows 构建支持文件
PCBuild :Windows 构建支持文件
Programs:“python”可执行文件和其他二进制文件的源代码
Python :CPython解释器源代码
Tools :用于构建或扩展 CPython 的独立工具
m4 : 用于自动配置 makefile 的自定义脚本在这里插入图片描述

  1. 需要将Cpython项目clone到我们的本机
    git clone git@github.com:python/cpython.git
    
    • 1
  2. 对于命令行编译脚本或Visual Studio解决方案,您需要安装几个外部工具、库和C头文件。(机器需要安装有python,python3+即可,我本机安装的python3.7.8)
  3. 在cpython目录下的PCBuild文件夹中有一个get_externals.bat文件,可以自动执行第二步的操作,安装的过程中可能会存在有些组件下载失败的原因,多尝试几次就好了。
    在这里插入图片描述
  4. 好的现在我们进入F:\source_code\cpython\PCbuild目录,该目录下有个pcbuild.sln文件,用vs双击打开即可
    在这里插入图片描述
  5. 打开后如图,根据自己本机是多少位就设置多少位,然后CTRL+shift+B开始生成项目(包括编译),visual studio常用快捷键
    在这里插入图片描述
  6. 按F5运行,如图,可以看到用vs编译的Cpython生成的python和本机的python运行结果是一致的,说明用vs编译成功Cpython
    在这里插入图片描述

4.需要注意debug模式下和release模式下编译的一些细节,这里我们用python中int对象的__sizeof__()来举例

  1. 首先我们得找到int对象的__sizeof__()在cpython中的源代码,如果之前有研究过cpython我们可以快速定位到对应的源代码在longobject.c中
    在这里插入图片描述

  2. 我们分别在debug模式下和release模式下对cpython进行编译运行

    • debug模式下,变量a大小位40字节
      在这里插入图片描述

    • release模式下,变量a大小位24字节,也就是说我们平时用的正式发布版本的(即本机安装的)64 位python,当a=0时占用的大小为24字节
      在这里插入图片描述

  3. debug和release模式下,为何同一个类型和值相同的变量,为何占用内存大小不一样呢,我们可以从源码入手来分析一波。
    从上面的分析我们可以知道__sizeof__()的最终结果为,且很容易知道当a=0时,Py_ABS(Py_SIZE(self))*sizeof(digit)的结果为0,那么a变量的大小即为offsetof(PyLongObject, ob_digit)的大小,宏offsetof(type, member-designator) 结果在一个常数整数 size_t 类型是一个结构成员的结构从一开始的字节偏移,在这里即ob_digit相对于PyLongObject结构体开始的偏移量

     res = offsetof(PyLongObject, ob_digit) + Py_ABS(Py_SIZE(self))*sizeof(digit);
    
    • 1

    回顾一下c库offset

    #include <stddef.h>
    #include <stdio.h>
    
    struct address {
        char name[50];
        char street[50];
        int phone;
    };
    
    int main() {
        printf("address 结构中的 name 偏移 = %d 字节。\n",
               offsetof(struct address, name));
    
        printf("address 结构中的 street 偏移 = %d 字节。\n",
               offsetof(struct address, street));
    
        printf("address 结构中的 phone 偏移 = %d 字节。\n",
               offsetof(struct address, phone));
    
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    执行结果如下
    在这里插入图片描述

  4. 所以最终就把问题转移到了PyLongObject上了,那么我们来看看PyLongObject的定义,按住Ctrl把鼠标发到PyLongObject,即可跳转到定义

    在这里插入图片描述

    在这里插入图片描述

  5. 由4我们可以对int___sizeof___impl增加一些调试信息,如下

    static Py_ssize_t
    int___sizeof___impl(PyObject *self)
    /*[clinic end generated code: output=3303f008eaa6a0a5 input=9b51620c76fc4507]*/
    {
        Py_ssize_t res;	printf("offsetof:%d,PyVarObject:%d,Py_ssize_t:%d,PyObject:%d,PyTypeObject*:%d\n", offsetof(PyLongObject, ob_digit),sizeof(PyVarObject),sizeof(Py_ssize_t),sizeof(PyObject),sizeof(PyTypeObject*));
        res = offsetof(PyLongObject, ob_digit) + Py_ABS(Py_SIZE(self))*sizeof(digit);
        return res;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
  6. 根据5中的修改用visual studio进行重新编译cpython可以知道release和debug下的结果为,可以知道debug和release下PyObject结构体大小相差了16字节。所以我们现在只需要重点研究PyObject的定义信息。

    debug
    //offsetof:40,PyVarObject:40,Py_ssize_t:8,PyObject:32,PyTypeObject*:8
    release
    //offsetof:24,PyVarObject:24,Py_ssize_t:8,PyObject:16,PyTypeObject*:8
    
    • 1
    • 2
    • 3
    • 4
  7. 从之前的分析可以知道PyObject结果如下,*很明显Py_ssize_t和_typeobject 均为8字节,那么多的16字节来自哪里呢,可以注意到_PyObject_HEAD_EXTRA

    typedef struct _object {
        _PyObject_HEAD_EXTRA
        Py_ssize_t ob_refcnt;
        struct _typeobject *ob_type;
    } PyObject;
    
    • 1
    • 2
    • 3
    • 4
    • 5
  8. 跳转到宏_PyObject_HEAD_EXTRA的定义如下
    在这里插入图片描述
    由上图可知当在Py_DEBUG模式(即为我们debug模式)下,会定义宏Py_TRACE_REFS,当定义了弘Py_TRACE_REFS时宏_PyObject_HEAD_EXTRA的具体类容为如下,当宏Py_TRACE_REFS存在时会定义指针变量_ob_next和_ob_prev

    #ifdef Py_TRACE_REFS
    /* Define pointers to support a doubly-linked list of all live heap objects. */
    #define _PyObject_HEAD_EXTRA            \
        struct _object *_ob_next;           \
        struct _object *_ob_prev;
    
    #define _PyObject_EXTRA_INIT 0, 0,
    
    #else
    #define _PyObject_HEAD_EXTRA
    #define _PyObject_EXTRA_INIT
    #endif
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    所以多的16字节的出处就很明显了,即指针_ob_next和_ob_prev,这两个指针主要作用是在debug模式下用于追踪所有活动堆对象的双向链表。这两个指针的详细用途后面我们再来慢慢研究,今天的分析就到这里

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/小丑西瓜9/article/detail/199303
推荐阅读
相关标签
  

闽ICP备14008679号