c++大型项目源码_python源码剖析——系列一

31bd0536ca45f0096736c4d06a0d9257.png

0 前言

Python 非常好用,哪怕一个没上过汇编,操作系统,编译原理等一系列基础计算机课程的人,也能快速上手。

再拿反面教材C++举例,引用和指针的区别入门阶段就搞懵了一批人。而指针和引用如果拓展开,C++老司机也是很容易翻车的。

Python好用的一个原因,就是把底层的很多复杂内容给封装简化了,当然很多动态语言也都再这么干(如PHP),只不过Python的用户体验大家一致觉得更好。

这个笔记系列,想从源码的角度来看,Python是如何把底层复杂内容进行封装的。

第一篇主要先讲大致框架,再拿int类型做一些展开。基于Python2.7的源码,Python3.0的源码会有区别,这个要注意。

1 万物皆对象,对象也为对象

先举个例子

Def 

Python中variable可以为任何东西,int, dict, list,string,function。

对小白来讲,写函数不用考虑变量类型,学习和使用体验是很好的。(当然,在大型项目重构的时候,发现函数无法确定变量类型,返回类型,是很蛋疼的事情。所谓,动态一时爽,全家火葬场)

这种操作,C++中叫多态,而多态必须有一个共同的父亲节点。同理,Python底层C实现也是多态,都有一个共同的父类。

也就是,万物皆对象,对象也为对象。

1.1 背景知识——C中的多态实现方式

typedef 

实现思路其实很简单,base class必须要是一个struct,继承类必须要在一开始就包含base struct。

1.2 对象三要素

对象三要素,引用计数,类型信息,类型内容。

这里从先从父亲节点说起,PyObject定义如下

[

注释已经说得很清楚,Nothing is actually declared to be a PyObject, but every pointer to a Python object can be cast to a PyObject*. This is inheritance built。

而根据上文多态的定义,子类在一开始包含PyObject_HEAD即可继承PyObject对象。

1.2.1 引用计数——Py_ssize_t ob_refcnt

内存回收机制中的核心变量,引用计数,细节不展开。

1.2.2 类型对象——struct _typeobject *ob_type

Python中万物皆对象有多彻底呢?用来指定一个对象类型的类型变量也是一个对象。

[

PyTypeObject就是类型对象,继承了PyObject。

这个对象通过大量的函数指针和多态来定义了python对象所应该具有的内容。

1.2.3 类型内容

PyObject做为父类肯定没有类型内容,但子类,例如int子类,int内容放那呢?

[

很明显,在PyObject_HEAD后,加上了long变量来存储整数内容。

同理,list,dict,string也是如此设计,当然变长对象的设计会更复杂。

1.3 Python对象的多态

类型对象PyTypeObject通过函数指针加多态来实现,这里拿printfunc来举例。

[

PyTypeObject 定义printfunc的接口,因为PyIntObject是PyObject的子类,所以可以在intobject中实现这个接口。换成string,dict,set等对象实现原理也一样。

通过这三要素,PyObject已经把对象框架搭完毕。如果我们要实现一个int对象,根据PyObject中的类型中定义的接口,选择我们所需来实现即可。

2 int型对象分析

int对象的接口实现想对简单,但也是有不少有意思的点。

2.1 int对类型对象的接口实现

[

可以看到,并不是类型对象所有定义的接口,int对象都需要实现,赋值为0即代表不用实现。

上文已拿int_print讲过了,更多代码细节建议去看源码。

2.2 整数内存池

对C来讲,栈的内存申请和销毁速度要比堆快的多,为什么就不展开了。

C中的int,bool等build-in变量都是在栈上操作。Python中万物皆对象,也就是struct,新建的int对象要通过malloc在堆上申请。

这样速度必然要比C慢一大截,并且日常代码中,整数类型的使用是非常频繁的。

所以,Python就引入内存池和内存块来进行加速。

2.2.1 小整数对象内存池

PyIntObject是不可变对象,所以可以提前申请内存池来存储常用的小数字,直接从内存池来拿就可以使用。

问题是,多小的整数算小整数呢?Python是可以自定义的。

[

代码如上,注释也说的比较清晰。

2.2.2 大整数对象内存块

小整数对象通过固定的内存池解决了内存重复申请的问题。大整数对象是Python申请了一块固定的内存块,这些内存块由大整数轮流使用。

核心是两个链表指针,分成四步走

[

第一步,整数如果为小整数,则直接从小整数内存池中取。

第二步,free_list如果不为null,则把free_list指向的空余内存分配给当前大数。

第三步,free_list如果为null,则申请一个PyIntBlock对象,一个PyIntBlock可以存多少个int对抗,量级可以自定义。

第四步,新申请的内存空间,用free_list串起来即可。具体参看intobject.c中的fill_free_list函数

还有两个关键步骤。

第一,Python是引用计数来释放内存,int类型内存释放后,free_list也要继续把这些free的内存串联起来。

第二,假如某个阶段int类型申请特别多,PyIntBlock自然也就申请了很多。然后某个阶段int被集中销毁,那么多个PyIntBlock是否完全保留,全都用free_list串起来?还是销毁大部分,只保留小部分?这块代码没细看。

这样的好处?

核心就一个,减少堆的碎片化。碎片化的坏处这里就不展开,Java中专门针对这个问题其实做了不少优化。

举个例子,C++的hash有一个内存碎片的问题,因为每个hash值指向的list都是用链表,链表的内存是分散的。对于超大的hash存储来讲,会导致堆的碎片化问题。

有一个优化的方式,就是让hash值指向的是个伪链表,实际上是个连续型内存。这个操作是不是看着跟上文的介绍有一点类似?

3 絮絮叨叨

这篇文章是基于三年前读《Python源码剖析》记的笔记,但一直没有完整的整理出来。

最近换工作,有了空闲时间,就花了几天时间整理了一下,还是挺有意思的。

毕竟身为策略工程师,天天用Python还是挺多的,对底层有一定了解还是挺好的。

后续的笔记自然就是继续把string,list,dict,再到虚拟机给写写,但抽空吧,可能又是三年后了呢。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值