Python 源碼閱讀:int
(點擊
上方藍字
,快速關注我們)
來源:伯樂在線 - wklken
如有好文章投稿,請點擊 → 這裡了解詳情
代碼我也僅僅是粗粗讀了一遍, 可能出現疏漏和理解錯誤, 發現瞭望指出哈.
示例
>>>
a
=
1
>>>
b
=
1
>>>
id
(
a
)
==
id
(
b
)
True
>>>
c
=
257
>>>
d
=
257
>>>
id
(
c
)
==
id
(
d
)
False
#在python2.x中, 對於大的序列生成, 建議使用xrange(100000) 而不是range(100000), why?
源碼位置 Include/intobject.h |
Objects/intobject.c
PyIntObject
typedef
struct
{
PyObject_HEAD
long
ob_ival
;
}
PyIntObject
;
結構
幾個構造方法
# 從字元串, 生成PyIntObject對象
PyAPI_FUNC
(
PyObject *
)
PyInt_FromString
(
char
*
,
char
**
,
int
);
# 從Py_UNICODE, 生成PyIntObject對象
#ifdef Py_USING_UNICODE
PyAPI_FUNC
(
PyObject *
)
PyInt_FromUnicode
(
Py_UNICODE*
,
Py_ssize_t
,
int
);
#endif
# 從long值, 生成PyIntObject對象
PyAPI_FUNC
(
PyObject *
)
PyInt_FromLong
(
long
);
PyAPI_FUNC
(
PyObject *
)
PyInt_FromSize_t
(
size_t
);
PyAPI_FUNC
(
PyObject *
)
PyInt_FromSsize_t
(
Py_ssize_t
);
這幾個方法, 只需要關注
# 因為大家最後都調用這個方法完成對象生成
PyAPI_FUNC
(
PyObject *
)
PyInt_FromLong
(
long
);
具體的構造方法 PyInt_FromLong
這個方法的定義
PyObject *
PyInt_FromLong
(
long
ival
)
{
register PyIntObject *
v
;
/* MARK: 如果, 值在小整數範圍內, 直接從小整數對象池獲取得到對象 */
#if NSMALLNEGINTS + NSMALLPOSINTS > 0
if
(
-
NSMALLNEGINTS
ival
&
ival
NSMALLPOSINTS
)
{
/* MARK: small_ints是什麼後面說 */
v
=
small_ints
[
ival
+
NSMALLNEGINTS
];
// 引用+1
Py_INCREF
(
v
);
/* 這裡先忽略, 計數 */
#ifdef COUNT_ALLOCS
if
(
ival
>=
0
)
quick_int_allocs
++
;
else
quick_neg_int_allocs
++
;
#endif
// 返回
return
(
PyObject *
)
v
;
}
#endif
// 如果free_list還不存在, 或者滿了
if
(
free_list
==
NULL
)
{
// 新建一塊PyIntBlock, 並將空閑空間鏈表頭部地址給free_list
if
((
free_list
=
fill_free_list
())
==
NULL
)
// 如果失敗, 返回
return
NULL
;
}
// 從free_list分出一個位置存放新的整數
/* Inline PyObject_New */
// 使用單向鏈表頭位置
v
=
free_list
;
// free_list指向單向鏈表下一個位置
free_list
=
(
PyIntObject *
)
Py_TYPE
(
v
);
// 初始化對象, 類型為PyInt_type, 值為ival
PyObject_INIT
(
v
,
&
PyInt_Type
);
v
->
ob_ival
=
ival
;
// 返回
return
(
PyObject *
)
v
;
}
注意這裡的Py_TYPE()方法, 在我們第一篇文章裡面有提到, 不知道的回去複習下對象的數據結構
#define Py_TYPE(ob) (((PyObject*)(ob))->ob_type)
簡而言之:
1.
先判斷數值是否是小整數
,
是的話從小整數對象池裡面直接返回
(
這個池固定大小,
下一點講
)
2.
如果不是
,
從通用整數對象池裡面取一個
,
初始化返回
(
如果這時候通用整數對象池還不存在或者已經滿了,
新建一個池加入維護
.
通用整數對象池後面講
)
小整數對象池
先看定義
#ifndef NSMALLPOSINTS
#define NSMALLPOSINTS 257
#endif
#ifndef NSMALLNEGINTS
#define NSMALLNEGINTS 5
#endif
#if NSMALLNEGINTS + NSMALLPOSINTS > 0
/* References to small integers are saved in this array
so that they can be shared.
The integers that are saved are those in the range
-NSMALLNEGINTS (inclusive) to NSMALLPOSINTS (not inclusive).
*/
static
PyIntObject *
small_ints
[
NSMALLNEGINTS
+
NSMALLPOSINTS
];
#endif
其實, 小整數對象池就是一個PyIntObject指針數組(注意是指針數組), 大小=257+5=262, 範圍是[-5, 257) 注意左閉右開. 即這個數組包含了262個指向PyIntObject的指針.
結構
創建整數時, 如果在[-5, 257)範圍, 直接返回已經存在的整數對象指針, 所以我們看到開頭的例子, id比較一個true/一個false
小整數對象池, 在一開始就初始化了, 其初始化代碼
int
_PyInt_Init
(
void
)
{
PyIntObject *
v
;
int
ival
;
// 注意這裡, free_list再次出現
#if NSMALLNEGINTS + NSMALLPOSINTS > 0
// 循環, 逐一生成
for
(
ival
= -
NSMALLNEGINTS
;
ival
ob_ival
=
ival
;
// 放到數組裡
small_ints
[
ival
+
NSMALLNEGINTS
]
=
v
;
}
#endif
return
1
;
}
代碼很眼熟吧, 覺得不眼熟回上面看代碼
結論
1.
小整數對象池緩存
[
-
5
,
257
)
內的整數對象
,
數值在這個範圍的整數對象有且只存在一個
...
2.
小整數對象池
,
只是一個指針數組
,
其真正對象依賴通用整數對象池
通用整數對象池1 – 基礎結構PyIntBlock
首先, 有個數據結構PyIntBlock
#define BLOCK_SIZE 1000 /* 1K less typical malloc overhead */
#define BHEAD_SIZE 8 /* Enough for a 64-bit pointer */
#define N_INTOBJECTS ((BLOCK_SIZE - BHEAD_SIZE) / sizeof(PyIntObject))
struct
_intblock
{
struct
_intblock *
next
;
PyIntObject
objects
[
N_INTOBJECTS
];
};
typedef
struct
_intblock
PyIntBlock
;
回憶一下PyIntObject結構(1個int, 1指針, 1個long), size=4+4+4(先這麼算), N_INTOBJECTS = 82
結構
通用整數對象池2 – 創建過程及運行時結構
有兩個指針
# 指向一個block
static
PyIntBlock *
block_list
=
NULL
;
# 指向一個PyIntObject
static
PyIntObject *
free_list
=
NULL
;
生成過程的定義
// 初始化一個PyIntBlock
static
PyIntObject *
fill_free_list
(
void
)
{
PyIntObject *
p
,
*
q
;
// 建立一個新的block
/* Python"s object allocator isn"t appropriate for large blocks. */
p
=
(
PyIntObject *
)
PyMem_MALLOC
(
sizeof
(
PyIntBlock
));
// 建立失敗(內存耗光了)
if
(
p
==
NULL
)
return
(
PyIntObject *
)
PyErr_NoMemory
();
// block_list指向新的PyIntBlock節點
((
PyIntBlock *
)
p
)
->
next
=
block_list
;
block_list
=
(
PyIntBlock *
)
p
;
/* Link the int objects together, from rear to front, then return
the address of the last int object in the block. */
// p=block裡面 PyIntObjects數組頭地址, q是尾地址
p
= &
((
PyIntBlock *
)
p
)
->
objects
[
0
];
q
=
p
+
N_INTOBJECTS
;
// 從尾部開始向首部移動, 利用對象里的ob_type指針(相當於使用這個欄位, ob_type不作為原來的用途), 建立起一個單向鏈表
// 這個單向鏈表的頭部是數組的最後一個
while
(
--
q
>
p
)
Py_TYPE
(
q
)
=
(
struct
_typeobject *
)(
q
-
1
);
Py_TYPE
(
q
)
=
NULL
;
// 單向鏈表最後一個元素的next指向null
// 返回單向鏈表的頭地址!!!
return
p
+
N_INTOBJECTS
-
1
;
}
新建第一個時, 只有一個
從裡面拿整數時, 取free_list指向的節點, 然後free_list指向鏈表下一個節點
當一個block用完了之後, 即free_list=NULL, 此時要新建另一個PyIntBlock
新建第二個
通用整數對象池3 – 刪除一個整數時
定義
#define PyInt_CheckExact(op) ((op)->ob_type == &PyInt_Type)
static
void
int_dealloc
(
PyIntObject *
v
)
{
// 是整數類型, 將對象放入free_list單向鏈表頭
if
(
PyInt_CheckExact
(
v
))
{
Py_TYPE
(
v
)
=
(
struct
_typeobject *
)
free_list
;
free_list
=
v
;
}
else
Py_TYPE
(
v
)
->
tp_free
((
PyObject *
)
v
);
//不是整數類型, 對應類型析構
}
可以看到, 回收的時候, 把空間給放回到free_list了, 後面接著用
block_list維護著所有PyIntBlock列表, 查看源碼注釋可以看到
PyIntBlocks are never returned
to
the
system before shutdown
(
PyInt_Fini
).
即, PyIntBlock申請的所有內存, 在Python結束之前, 都不會被釋放
所以
,
使用
range
(
100000
),
運行後
,
雖然程序結束了
,
但是整數佔用空間還在
.
建議對大範圍的序列生成使用
xrange
python3
.
x
不用擔心這個問題本系列
《Python 源碼閱讀:類型》
《Python 源碼閱讀:對象》
《Python 源碼閱讀:String》
看完本文有收穫?請轉
發分享給更多人
關注「P
ython開發者」,提升Python技能
※Python 中的作用域規則和閉包簡析
※Python 源碼閱讀: String
※Python 中的屬性訪問與描述符
※Python 源碼閱讀:對象
※構建多層感知器神經網路對數字圖片進行文本識別
TAG:Python開發者 |