内部结构#
float 实例对象在 Include/floatobject.h 中定义如下:
1
2
3
4typedef struct {
    PyObject_HEAD   //定长对象共用的头部
    double ob_fval;  //额外字段,存储对象所承载的浮点值
} PyFloatObject;
float 类型对象#
与实例对象不同, float 类型对象 全局唯一 ,因此可以作为 全局变量 定义。 在 C 文件 Objects/floatobject.c 中,我们找到了代表 float 类型对象的全局变量 PyFloat_Type :
1  | PyTypeObject PyFloat_Type = {  | 
PyFloat_Type 中保存了很多关于浮点对象的 元信息
PyFloat_Type 很重要,作为浮点 类型对象 ,它决定了浮点 实例对象 的 生死和行为 。
对象的创建#
调用类型对象 float 创建实例对象: Python 执行的是 type 类型对象中的 tp_call 函数。 tp_call 函数进而调用 float 类型对象的 tp_new 函数创建实例对象, 再调用 tp_init 函数对其进行初始化:
除了通用的流程, Python 为内置对象实现了对象创建 API
,简化调用,提高效率:
1
2
3
4
5PyObject *
PyFloat_FromDouble(double fval);  /*通过浮点值创建浮点对象*/
PyObject *
PyFloat_FromString(PyObject *v);  /*通过字符串对象创建浮点对象*/
以 PyFloat_FromDouble 为例,特化的对象创建流程如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22PyObject *
PyFloat_FromDouble(double fval)
{
    PyFloatObject *op = free_list;
    /*为对象 分配内存空间,优先使用空闲对象缓存池 */
    if (op != NULL) {                 
        free_list = (PyFloatObject *) Py_TYPE(op);
        numfree--;
    } 
    /*空闲对象缓存池为空,调用PyObject_MALLOC申请内存 */
    else {
        op = (PyFloatObject*) PyObject_MALLOC(sizeof(PyFloatObject));
        if (!op)
            return PyErr_NoMemory();
    }
    /* Inline PyObject_New */
    (void)PyObject_INIT(op, &PyFloat_Type);
    /*将 ob_fval 字段初始化为指定 浮点值 */
    op->ob_fval = fval;    
    return (PyObject *) op;
}
上面用到的宏 PyObject_INIT 在头文件 Include/objimpl.h 中定义为:
1
2#define PyObject_INIT(op, typeobj) \
    ( Py_TYPE(op) = (typeobj), _Py_NewReference((PyObject *)(op)), (op) )  //前半部分调用  Py_TYPE(op) 初始化 对象类型 字段 ob_type,后面语句初始化 引用计数 字段 ob_refcnt
上面提到的宏定义 Py_TYPE,位于 Include/object.h
头文件:
1
#define Py_TYPE(ob) (((PyObject*)(ob))&ob_type)
宏 _Py_NewReference,在 Include/Object.h 中定义:
1
2
3
4#define _Py_NewReference(op) (                          \
    _Py_INC_TPALLOCS(op) _Py_COUNT_ALLOCS_COMMA         \
    _Py_INC_REFTOTAL  _Py_REF_DEBUG_COMMA               \
    Py_REFCNT(op) = 1)   /*将对象引用计数初始化为 1*/
# 对象的销毁 当对象不再需要时, Python 通过 Py_DECREF 或者
Py_XDECREF 宏减少引用计数; 当引用计数降为 0 时, Python 通过
**_Py_Dealloc** 宏回收对象:
 
  | 
空闲对象缓存池#
浮点运算背后涉及 大量临时对象创建以及销毁 ,以下面计算为例:
1
>>> area = pi * r ** 2
创建对象时需要分配内存,销毁对象时又需要回收内存。 大量临时对象创建销毁 ,意味着 大量内存分配回收操作 ,这显然是是不可接受的。
因此 Python 在浮点对象销毁后,并不急于回收内存,而是将对象放入一个 空闲链表 。 后续需要创建浮点对象时,先到空闲链表中取,省去分配内存的开销。
浮点对象的空闲链表同样在 Objects/floatobject.c 中定义:1  | #ifndef PyFloat_MAXFREELIST  | 
因此创建浮点对象时,可以从 链表中取出空闲对象,省去
申请内存的开销! 以 PyFloat_FromDouble 为例:
1
2
3
4
5
6
7
8PyFloatObject *op = free_list;                  /*op指向第一个 空闲对象*/
if (op != NULL) {
    free_list = (PyFloatObject *) Py_TYPE(op);  /*free_list指向第一个 空闲对象(op)的ob_type所指向的 下一个空闲对象(相当于链表的头部删除)*/
    numfree--;                                  /*更新空闲链表维护的数量*/
} else {
    op = (PyFloatObject*) PyObject_MALLOC(sizeof(PyFloatObject));    /*free_list为空时,重新分配内存*/
    // ...
}
对象销毁时, Python 将其缓存在空闲链表中,以备后用。考察
float_dealloc 函数:
1
2
3
4
5
6
7if (numfree >= PyFloat_MAXFREELIST)  {   /*销毁时,判断free_list是否达到最大容量*/
    PyObject_FREE(op);                   /*回收对象内*/
    return;
}
numfree++;                                      
Py_TYPE(op) = (struct _typeobject *)free_list;  /*op的ob_type指向当前 第一个空闲对象*/
free_list = op;                                 /* free_list指向op(相当于链表的头部插入)*/
对象的行为#
PyFloat_Type 中定义了很多函数指针,包括 tp_repr 、 tp_str 、 tp_hash
等。 这些函数指针将一起决定 float 对象的行为,例如 tp_hash
函数决定浮点哈希值的计算:
1
2
3>>> pi = 3.14
>>> hash(pi)
3228180212899174431
2
3
4
5static Py_hash_t
float_hash(PyFloatObject *v)
{
    return _Py_HashDouble(v->ob_fval);
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21typedef struct {
    /* Number implementations must check *both*
    arguments for proper type and implement the necessary conversions
    in the slot functions themselves. */
    binaryfunc nb_add;
    binaryfunc nb_subtract;
    binaryfunc nb_multiply;
    binaryfunc nb_remainder;
    binaryfunc nb_divmod;
    ternaryfunc nb_power;
    unaryfunc nb_negative;
    // ...
    binaryfunc nb_inplace_add;
    binaryfunc nb_inplace_subtract;
    binaryfunc nb_inplace_multiply;
    binaryfunc nb_inplace_remainder;
    ternaryfunc nb_inplace_power;
    //...
} PyNumberMethods;
- 一元函数 ( unaryfunc ): 需要传入一个参数的函数。
 - 二元函数 ( binaryfunc): 需要传入两个参数的函数。
 
回到 Objects/floatobject.c 观察浮点对象数值操作集 float_as_number
是如何初始化的:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17static PyNumberMethods float_as_number = {
    float_add,          /* nb_add */
    float_sub,          /* nb_subtract */
    float_mul,          /* nb_multiply */
    float_rem,          /* nb_remainder */
    float_divmod,       /* nb_divmod */
    float_pow,          /* nb_power */
    (unaryfunc)float_neg, /* nb_negative */
    // ...
    0,                  /* nb_inplace_add */
    0,                  /* nb_inplace_subtract */
    0,                  /* nb_inplace_multiply */
    0,                  /* nb_inplace_remainder */
    0,                  /* nb_inplace_power */
    // ...
};1
2
3
4>>> a = 1.5
>>> b = 1.1
>>> a + b
2.61
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16static PyObject *
float_add(PyObject *v, PyObject *w)
{
    double a,b;
    //将两个参数对象转化成浮点值,CONVERT_TO_DOUBLE是一个宏,将PyFloatObject里面的ob_fval抽出来给double变量
    CONVERT_TO_DOUBLE(v, a);
    CONVERT_TO_DOUBLE(w, b);
    PyFPE_START_PROTECT("add", return 0)
    //对两个浮点值求和
    a = a + b;
    PyFPE_END_PROTECT(a)
    //创建一个新浮点对象保存计算结果并返回
    return PyFloat_FromDouble(a);
}
所以如果是 C中的两个浮点数相加,直接a + b就可以了,编译之后就是一条简单的机器指令,然而 Python则需要额外做很多其它工作。从一个简单的加法上面就可以看出来Python为什么会比C慢几十倍了。