哈哈哈,看的有點兒興奮了,筆記下:
在python的內部同時維護著巨集和記憶體管理函式兩種記憶體管理機制,巨集定義可以節省一次函式呼叫的開銷,
提高執行效率,但同時,使用巨集是危險的,因為隨著python的演進,記憶體管理機制的實現可能會發生改變,因為巨集的名字雖然是不會改變的,但其實現**可能會發生改變,導致舊的巨集編寫的c擴充套件與新版本的python產生二進位制不相容,這樣十分危險,所以,在python的內部維護了函式和巨集兩套介面,自己編寫擴充套件時,最好使用函式
1 小塊的記憶體池
在python執行的過程中,很多時候都是小塊記憶體的申請,申請後又很快釋放,也就意味著大量的malloc和free
操作,導致作業系統頻繁的在使用者態和核心態之間進行切換,影響py的執行效率,為了解決這個問題py引入了快取池的機制(pymalloc),整個小塊記憶體的記憶體池可以視為乙個層次結構,在這個層次可以分為4層,從上到下依次為:block , pool , arean和記憶體池
1.1 先看下block吧:
在最底層block是乙個確定大小的記憶體,在py中有很多種block,每個block有不同的記憶體大小,這個記憶體的大小稱為size class,可以不記得,記得這是最小單元就好,所有block事8位元組對齊的,同時,py為block的大小設定了乙個上限,當申請的記憶體大小小於這個上限時,py可以使用不同種類的block來滿足需求,大於這個上限的時候,py轉交給第一次的記憶體管理機制來處理,即pymem函式族,在py2.5版本,這個值為256k
不同種類的block的大小分別為 8,16,32 ..... ,256,每個size class對應乙個size class index ,這個index從0開始,假設申請記憶體為28,會得到乙個32的block,能滿足的最小值
1.2 那麼超過256的記憶體申請怎麼辦呢 ? 來一起看下pool吧:
/* pool for small blocks. */
struct pool_header ref;/* number of allocated blocks */
block *freeblock;/* pool's free list head */
struct pool_header *nextpool;/* next pool of this size class */
struct pool_header *prevpool;/* previous pool "" */
uint arenaindex;/* index into arenas of base adr */
uint szidx; /* block size class index */
uint nextoffset;/* bytes to virgin block */
uint maxnextoffset;/* largest valid nextoffset */};
一組block的集合稱為pool,也就是乙個pool管理著多個有固定大小的block,乙個pool的大小通常是乙個記憶體頁,py將其定義為4k,乙個pool裡邊的block的大小是固定的,比如,可能管理了100個32k的block,也可能管理了100個64k的block,但是不可能乙個pool裡既有32k的block也有64k的block,每乙個pool都和乙個size聯絡到一起,更確切的說是和乙個size class index聯絡到一起,這就是pool header裡邊的sindex的意義所在
一起看下pool是怎麼把block組合到一起的:/*
* initialize the pool header, set up the free list to
* contain just the second block, and return the first
* block.
*/pool->szidx = size;
size = index2size(size);
bp = (block *)pool + pool_overhead;
pool->nextoffset = pool_overhead + (size << 1);
pool->maxnextoffset = pool_size - size;
pool->freeblock = bp + size;
*(block **)(pool->freeblock) = null;
unlock();
return (void *)bp;
pool中的第一塊記憶體是分配給pool_header的,所以pool的結構體中ref.count是1,第一塊位址被分配後返回bp的是乙個實際位址(也就是申請pool之後的返回),在這個位址之後會有進4k的記憶體都是實際可用的,但是可以肯定的是申請記憶體的函式只會使用[bp,bp+size]這個區間的記憶體,這是size class index保證的,
我該怎麼畫圖呢 ? ......先跳過了/*
* there is a used pool for this size class.
* pick up the head block of its free list.
*/++pool->ref.count;
bp = pool->freeblock;
assert(bp != null);
if ((pool->freeblock = *(block **)bp) != null)
/* * reached the end of the free list, try to extend it.
*/if (pool->nextoffset <= pool->maxnextoffset)
假設連續申請5塊大小為28位元組的記憶體,由於28位元組的對應size class index為3,所以會申請5塊32位元組的記憶體,原來freeblock執行的是下乙個可用bock的起始位址,再次申請是,只需要返回freeblock指向的位址就可
以了,很顯然,freeblock需要前進,指向下乙個可用block,此時nextoffset就發光發熱了,在pool header裡,nextoffset和maxoffset是對pool中的block步進迭代的變數,偏移位址恰好是下乙個block的可用位址,在分配
完乙個block之後,freeblock和nextoffset就移動乙個block的距離,maxoffset則定義了最大的可用大小,劃定了block的邊界,nextoffset > maxoffset的時候,pool中的block就遍歷完了,恩,就是這樣的,申請->前進->
申請->前進......
但是假如乙個block用完釋放了,假如申請了連續5塊32位元組的記憶體,2用完釋放了,下乙個申請是用2還是6 ?
一旦py執行會有大量的這種記憶體碎片產生,py必須用一種機制把離散的記憶體管理起來,這就是自由的block鍊錶,鍊錶的關鍵就是header中的freeblock,回到上邊,pool申請初始化完成之後,會指向乙個有效位址,為下乙個可分配的記憶體位址,還設定了乙個*freeblock,等到有記憶體釋放的時候,freeblock就會變成乙個小精靈,
在這4k的記憶體上靈活舞動,哈哈~~
pool = pool_addr(p);
if (py_address_in_range(p, pool))
來看下,block釋放的時候,freeblock指向乙個有效的pool位址,但是*freeblock是null,假設釋放的block是blocka,在pool->freeblock = (block *)p;的時候,freeblock的值被更新了,指向了blocka的首位址,在釋放下乙個block的時候,同樣通過
*(block **)p = lastfree = pool->freeblock;
pool->freeblock = (block *)p;
把釋放的block連線到一起,形成自由鍊錶,在需要記憶體的時候可以很方便的遍歷這個鍊錶,優先使用被釋放的記憶體,當pool->freeblock為null的時候就不存在離散的自由鍊錶了,分配nextblock即可,nextoffset > maxoffset時,乙個pool就用完了,怎麼辦 ?哈哈,莫著急,一定是存在乙個pool的集合的,這就是下邊要說的arean
Python記憶體管理機制
一 python記憶體 因為要呼叫while迴圈,迴圈內有temp變數,不清楚python是否會在每一輪迴圈結束後自動釋放temp記憶體空間,做了乙個記憶體測試,發現無論temp none,還是del temp,只能銷毀變數,無法完全釋放記憶體空間。注 紅色部分標出相同記憶體id。python vi...
python記憶體管理機制
a 1整數1為乙個物件。而a是乙個引用。利用賦值語句,引用a指向物件1。在python中,整數和短小的字元,python都會快取這些物件,以便重複使用。當我們建立多個等於1的引用時,實際上是讓所有這些引用指向同乙個物件。a 1 b 1 print id a print id b 在python中,每...
python記憶體管理機制
1.引用計數 當乙個python物件被引用時 其引用計數增加 1 當其不再被變數引用時 引用計數減 1 當物件引用計數等於 0 時,物件被刪除 引用計數是一種非常高效的記憶體管理機制 2.垃圾 垃圾 機制 引用計數 標記清除 分帶 引用計數 引用計數也是一種垃圾收集機制,而且也是一種最直觀,最簡單的...