1. 程式人生 > 其它 >2.1 Python3 float詳解

2.1 Python3 float詳解

float內部結構

首先在檔案Include/floatobject.h中,找到了float例項物件的結構體:

typedef struct {
    PyObject_HEAD
    double ob_fval;
} PyFloatObject;

除了定長物件的共用頭部,只有一個欄位ob_fval,這個欄位就是用來儲存浮點物件的浮點值的。

在回顧一下float型別物件的結構體。float型別物件是系統內建的型別物件,是全域性唯一的,因此可以作為全域性變數定義。在檔案Objects/floatobject.c中:

PyTypeObject PyFloat_Type = {
    PyVarObject_HEAD_INIT(&PyType_Type, 0)
    "float",
    sizeof(PyFloatObject),
    0,
    (destructor)float_dealloc,                  /* tp_dealloc */
    0,                                          /* tp_vectorcall_offset */
    0,                                          /* tp_getattr */
    0,                                          /* tp_setattr */
    0,                                          /* tp_as_async */
    (reprfunc)float_repr,                       /* tp_repr */
    &float_as_number,                           /* tp_as_number */
    0,                                          /* tp_as_sequence */
    0,                                          /* tp_as_mapping */
    (hashfunc)float_hash,                       /* tp_hash */
    0,                                          /* tp_call */
    0,                                          /* tp_str */
    PyObject_GenericGetAttr,                    /* tp_getattro */
    0,                                          /* tp_setattro */
    0,                                          /* tp_as_buffer */
    Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE |
        _Py_TPFLAGS_MATCH_SELF,               /* tp_flags */
    float_new__doc__,                           /* tp_doc */
    0,                                          /* tp_traverse */
    0,                                          /* tp_clear */
    float_richcompare,                          /* tp_richcompare */
    0,                                          /* tp_weaklistoffset */
    0,                                          /* tp_iter */
    0,                                          /* tp_iternext */
    float_methods,                              /* tp_methods */
    0,                                          /* tp_members */
    float_getset,                               /* tp_getset */
    0,                                          /* tp_base */
    0,                                          /* tp_dict */
    0,                                          /* tp_descr_get */
    0,                                          /* tp_descr_set */
    0,                                          /* tp_dictoffset */
    0,                                          /* tp_init */
    0,                                          /* tp_alloc */
    float_new,                                  /* tp_new */
    .tp_vectorcall = (vectorcallfunc)float_vectorcall,
};

PyFloat_Type儲存了float物件的元資訊,這些元資訊決定了浮點例項物件的生死和行為,關鍵欄位如下:

  • tp_name:型別的名稱,這裡是常量'float';
  • tp_dealloc、tp_init、tp_alloc、tp_new:物件建立和銷燬的相關函式;
  • tp_repr:生成語法字串表示形式的函式;
  • tp_str:生成普通字串表示形式的函式;
  • tp_as_number:數值操作集;
  • tp_hash:雜湊值生成函式;

float例項的建立

float例項物件的建立流程前面的章節已經介紹過了,再來回顧一下使用通用流程建立物件的過程:Python執行的是type型別物件當中的tp_call函式。tp_call函式進而呼叫float型別物件的tp_new和tp_init函式建立例項物件並進行初始化。

在原始碼中,PyFloat_Type的tp_init函式指標為空,這是因為float是一種很簡單的物件,初始化操作就是一個賦值語句,在tp_new中完成即可。

除了通用流程,Python為內建物件實現了物件建立API,簡化呼叫,提高效率。比如直接建立浮點物件:

>>> pi = 3.14

這裡其實是通過PyFloat_FromDouble函式實現的,直接將浮點值建立成浮點物件:

PyObject *
PyFloat_FromDouble(double fval)
{
    PyFloatObject *op = free_list;
    if (op != NULL) {
        free_list = (PyFloatObject *) Py_TYPE(op);
        numfree--;
    } else {
        op = (PyFloatObject*) PyObject_MALLOC(sizeof(PyFloatObject));
        if (!op)
            return PyErr_NoMemory();
    }
    /* Inline PyObject_New */
    (void)PyObject_INIT(op, &PyFloat_Type);
    op->ob_fval = fval;
    return (PyObject *) op;
}
  • 首先為物件分配記憶體空間(PyObject_MALLOC函式),優先使用空閒物件快取池。
  • 初始化物件型別欄位ob_type以及引用計數字段ob_refcnt(PyObject_INIT);
  • 將ob_fval欄位初始化為浮點值。

float例項的銷燬

當物件的某次引用被解除時,Python通過Py_DECREF或者Py_XDECREF巨集減少引用計數;當引用計數降為0時,Python通過_Py_Dealloc巨集回收物件。

_Py_Dealloc巨集呼叫型別物件PyFloat_Type中的tp_dealloc函式指標:

#define _Py_Dealloc(op) (                               \
    _Py_INC_TPFREES(op) _Py_COUNT_ALLOCS_COMMA          \
    (*Py_TYPE(op)->tp_dealloc)((PyObject *)(op)))

根據程式碼可知,float回收物件實際呼叫的函式是float_dealloc:

static void
float_dealloc(PyFloatObject *op)
{
    if (PyFloat_CheckExact(op)) {
        if (numfree >= PyFloat_MAXFREELIST)  {
            PyObject_FREE(op);
            return;
        }
        numfree++;
        Py_TYPE(op) = (struct _typeobject *)free_list;
        free_list = op;
    }
    else
        Py_TYPE(op)->tp_free((PyObject *)op);
}

總結一下,float例項物件從建立到銷燬整個生命週期所涉及的關鍵函式、巨集以及呼叫關係如下:

空閒物件快取池

浮點運算是比較常見的運算方式之一。其實浮點運算背後涉及大量臨時物件建立和銷燬的動作,比如計算圓周率:

>>> area = pi * r ** 2

該語句首先計算r**2,即半徑的平方,中間結果由一個臨時物件來儲存,假如是變數a,然後計算圓周率pi和a的乘積,將最後的結果賦值給變數area,最後,銷燬臨時物件a。

可見這樣一條簡單的浮點運算就隱藏了一個臨時物件的建立和銷燬,如果是複雜的資料運算將涉及大量的物件的建立和銷燬,而這就意味著大量的記憶體分配和回收操作,這是及其耗效能的。

Python考慮了這種情況,在銷燬浮點物件後,並沒有立刻回收記憶體,而是將物件放入一個空閒連結串列中,後續需要建立浮點物件時,可以先從空閒連結串列中取,省去了分配記憶體的開銷。

在檔案Objects/floatobject.c中可以看到浮點物件空間連結串列的定義:

#ifndef PyFloat_MAXFREELIST
#define PyFloat_MAXFREELIST    100
#endif
static int numfree = 0;
static PyFloatObject *free_list = NULL;
  • free_list變數,指向空閒連結串列頭節點的指標;
  • numfree變數,維護空閒連結串列 當前長度;
  • PyFloat_MAXFREELIST巨集,限制空閒連結串列的最大長度,避免佔用過多的記憶體;

為了不新增額外的連結串列指標,free_list把ob_type欄位當做next指標來用,將空閒物件串成連結串列;

以PyFloat_FromDouble為例:

PyFloatObject *op = free_list;
if (op != NULL) {
    free_list = (PyFloatObject *) Py_TYPE(op);
    numfree--;
} else {
    op = (PyFloatObject*) PyObject_MALLOC(sizeof(PyFloatObject));
    // ...
}

分配記憶體的流程如下:

  1. 檢查free_list是否為空;
  2. 如果free_list非空,則取出頭節點備用,並將numfree減一,並通過Py_TYPE函式(獲取物件的型別物件)取出free_list頭部的ob_type欄位(即第二個空閒物件的地址),將free_list指標指向新的頭部;
  3. 如果free_list為空,則呼叫PyObject_MALLOC分配記憶體。

如此,每當需要建立浮點物件時,可以從連結串列中取出空閒物件,省去申請記憶體的開銷。而當float物件被銷燬時,Python將其快取在空閒連結串列中,以備後用,程式碼如下:

if (numfree >= PyFloat_MAXFREELIST)  {
    PyObject_FREE(op);
    return;
}
numfree++;
Py_TYPE(op) = (struct _typeobject *)free_list;
free_list = op;

主要流程便是判斷空閒連結串列長度是否達到了限制值,如果達到了,則直接回收物件記憶體,如果未達到,則將物件插到空閒連結串列頭部,並使得numfree加一。

以上部分便是Python空閒物件快取池的介紹,該機制對提高物件分配效率發揮著很重要的作用。