Python記憶體池管理與緩衝池設計

來源:互聯網
上載者:User

Python是一門開發效率很高的語言,而且其既下裡巴人,又陽春白雪。也就是說這門語言只要稍加學習就可以上手開發,而深入探究也會發現Python有很多高深的東西。最近讀了《Python源碼剖析》,收穫良多,今天就把Python的記憶體管理整理一番。

本文的組織如下:
第一部分:整理Python的記憶體管理機制,主要包括記憶體池以及對象緩衝池
第二部分:從百度的筆試題來探討如何?一個緩衝池。


第一部分:整理Python的記憶體管理機制Python的記憶體管理記憶體總共分為4層,如所示


其中Layer2為記憶體池,Layer3為對象緩衝池。Python的對象緩衝池Layer3是建立在Layer2基礎上的。

本文重點總結記憶體池和對象緩衝池。


記憶體池

ython又分為大記憶體和小記憶體。大小以256位元組為界限,對於大記憶體使用量Malloc進行分配,而對於小記憶體則使用記憶體池進行分配。

 Python的記憶體池又分為4個層次:Block、Pool、Arean、usedpool,如所示



其中block是最小的記憶體單元,大小為8的整數倍。如果想申請27B的記憶體,會分配一個32B的block,其中申請size和size_index之間的關係有對應,見




其中block是最小的記憶體單元,大小為8的整數倍。如果想申請27B的記憶體,會分配一個32B的block,其中申請size和size_index之間的關係有對應,見





對象緩衝池
整數對象緩衝池

對於[-5,257]這樣的小整數,系統已經初始化好,可以直接拿來用。而對於其他的大整數,系統則提前申請了一塊記憶體空間,等需要的時候在這上面建立大整數對象。


最上面的為PyInBlock結構,每個PyInBlock中是一個PyIntObject數組。

而連續數組在記憶體不斷進行申請和釋放中就會出現不連續的空閑塊,因此需要把所有的空閑記憶體塊組織起來,這樣就需要使用free_list進行組織

2. String對象緩衝池

Python為256個Ascii碼進行了對象緩衝池,見

這裡面的過程是先對所建立的字元對象進行Intern機制,再將intern的結果緩衝到字串緩衝池characters中。所謂的intern機制是指在系統中建立一個(key,value)映射關係的集合,記錄著被intern機制處理過的PyStringObject對象。對於被Intern之後的字串,在整個Python運行時,系統中都只有唯一的與該字串對應的PyStringObject對象。這樣當判斷兩個PyStringObject對象是否相同時,如果它們都被Intern了,那麼只需要簡單地檢查它們對應的PyObject*是否相同即可。這個機制既節省了空間,又簡化了對PyStringObject對象的比較

3. List和Dict對象緩衝池

第二部分 設計緩衝池


說完了Python的記憶體管理,我們再來探討一下如何設計並實現一個對象緩衝池。首先來看一下百度的一個筆試題。

設計一個緩衝池,用於存放系統所需要的資源。滿足如下要求:
(1)當讀取緩衝池資源是,如果沒有該資源,則建立該資源,放入緩衝池中。
(2)緩衝池可以存放各種形式的資源。
(3)要有重新整理機制,當一個資源長時間沒有使用時,要把該資源從緩衝池中剔除。
要考慮分配資源的合理性和時效性,緩衝池可以有的參數有最小資源數、最大資源數、timeout等,重點描述一下緩衝池的重新整理機制。

先要知道緩衝池是用來幹嘛的!所謂緩衝池是為了減少磁碟的IO操作,專門在記憶體中開闢一塊地區,將磁碟中一些經常訪問的資料放入到該地區,以檢查IO操作。
因此設計一個緩衝池需要考慮的幾個問題:
a) 如何對資料庫進行組織,使得可以快速尋找到緩衝池中的資源
b) 當緩衝池滿的時候,使用什麼樣的換入換出策略,如何保證資料區塊置換的效率
c) 對同一個資料區塊的並發訪問

d)維護資料一致性,採用什麼樣的寫入策略

緩衝池的概貌

這裡的hash bucket就是記憶體二維數組的第一維。它是通過對buffer header裡記錄的資料區塊地址和資料區塊類型運用hash演算法以後,得到的組號。
這裡的hash chain就是屬於同一個hash bucket的所有buffer header所串起來的鏈表。實際上,hash bucket只是一個邏輯上的概念。每個hash bucket都是通過不同的hash chain而體現出來的。每個hash chain都會由一個cache buffers chains latch來管理其並行作業。
而對於buffer header來說,每一個資料區塊在被讀入buffer cache時,都會先在buffer cache中構造一個buffer header,buffer header與資料區塊一一對應。


重新整理機制


本文重點討論一下重新整理機制。所謂重新整理即當一個資源長時間沒有使用時,要把該資源從緩衝池中剔除,怎麼樣才能判定一個資源長時間沒有使用呢?參考作業系統中Cache的設計機制,cache中常用換頁機制,採用的有FIFO、LRU、Clock演算法。這裡我們就是用LRU演算法。

LRU

我們舉一個例子。假設緩衝池只能容納4個資料區塊,同時只有一個hash chain和一個LRU。當系統剛剛啟動,緩衝池是空的。這時前台進程擷取資料區塊,系統找一個空的記憶體資料區塊,並將其對應的buffer header掛到hash chain上。同時,系統還會把該buffer header掛到LRU的最尾端。隨後前台進程又發出擷取資料區塊請求,這時所找到的buffer header在LRU上會掛到前一個buffer header的後面,也就是說請求所找到的buffer
header現在變成了LRU的最尾端了。假設發出4資料請求以後找到了4個buffer header,從而用完了所有的buffer cache空間。這個時候的LRU可以用來表示。

這個時候,進程發來了第5個資料區塊請求語句。這時的緩衝池裡已經沒有空的記憶體資料區塊了。但是既然需要容納下第五個資料區塊,就必然需要找一個可以被替換的記憶體資料區塊。這個記憶體資料區塊會到LRU上去找。按照系統設定的最近最少使用的原則,位於LRU最尾端的BH1將成為犧牲者,系統會把該BH1對應的記憶體資料區塊的內容清空,並將當前所獲得的資料區塊的內容拷貝進去。這個時候,BH1就成了LRU的首端,而BH2則成為了LRU的尾端。如所示。在這種方式下,經常被訪問的資料區塊可以一直靠近LRU的首端,也就保證了這些資料區塊可以儘可能的不被替換掉,從而保證了訪問的效率。

加入訪問次數的LRU

OK,假如我們想把每個資料區塊的訪問次數加入到資料區塊置換策略中,該如何?呢?
我們來增加一個輔助鏈表。緩衝池有LRU和LRUW兩個鏈表,分別叫做輔助鏈表和主鏈表。同時還對buffer header增加了一個屬性:touch數量,也就是每個buffer header曾經被訪問過的次數,來對LRU鏈表進行管理。系統每訪問一次buffer header,就會將該buffer header上的touch數量增加1,因此,touch數量“近似”的體現了某個記憶體資料區塊總共被訪問的次數。注意,這隻是近似,並不精確。因為touch的增加並沒有使用鎖來管理並發性。這隻是一個大概值,表示趨勢的,不用百分百的精確。

還是用上面的這個例子來說明。還是假設buffer cache只能容納4個資料區塊,同時只有一個hash chain和一個LRU(確切的說應該是一對LRU主鏈表和輔助鏈表)。讀入第一個資料區塊時,該資料區塊對應的buffer header會掛到LRU輔助鏈表(注意,這裡是輔助鏈表,而不是主鏈表)的最末端,同時touch數量為1。讀取第二個不同的資料區塊時,該資料區塊對應的buffer header會掛到前一個buffer header的後面,從而位於LRU輔助鏈表的最末端,同樣touch為1。假設4個資料區塊全都用完以後的LRU鏈表可以用四描述。每個buffer
header的touch數量都為1。

中我們可以看到輔助LRU鏈表都掛滿了,而主LRU鏈表還是空的。這個時候,系統要求返回指定的資料區塊。系統發現buffer cache裡已經沒有空的記憶體資料區塊了,於是從輔助LRU鏈表的尾部開始掃描,也就是從BH1開始掃描,以尋找可以被替代的資料區塊。這時將選出BH1作為犧牲者,並將其對應的記憶體資料區塊的內容清空,同時將當前第五個資料區塊的內容拷貝進去。但是這裡要注意,這個時候該BH1在LRU鏈表上的位置並不會發生任何的變化。而不會之前的那樣,BH1變成LRU鏈表的首端。

接下來,系統發出兩次資料區塊請求,分別要返回與第5個和第4個一樣的資料區塊,也就是要返回當前的BH1和BH4。這個時候,oracle會增加BH1和BH4的touch數量,同時將該BH1和BH4從輔助LRU鏈表上摘下,轉移到主LRU鏈表的中間位置。可以用描述。

這個時候,如果發來了第個資料區塊請求,要求返回與第3個相同的資料區塊,也就是當前的BH3,則這時該BH3會插入主LRU鏈表上的BH1和BH4中間,注意每次向主LRU列表插入buffer header時都是向中間位置插入。如果發來了第九句SQL要求返回BH2,則我們可以知道,BH2會轉移到主LRU鏈表的中間。這個時候,輔助LRU鏈表就空了,沒有buffer header了。

這時,如果又發來第10個資料區塊請求,要求返回一個新的、buffer cache中不存在所需內容的資料區塊時。oracle會先掃描輔助LRU鏈表,發現上面沒有任何的buffer header時,則必須掃描主LRU鏈表。從尾部開始掃描,採用前面說到的與掃描輔助LRU鏈表相同的規則挑選犧牲者。挑出的可以被替代的buffer header將從主LRU鏈表上摘下,放入輔助LRU鏈表。

從上面所描述的buffer header在輔助LRU鏈表和主LRU鏈表之間交替的過程中,這種改進LRU鏈表的管理方式的目的,就是想千方百計的能夠將多次被訪問的資料區塊保留在記憶體裡,同時又要平衡有限的記憶體資源。這種方式相比較之前而言,無疑是進步很多的。在之前中,某個資料區塊可能只會被訪問一次,但是就這麼一次的訪問就將該資料區塊放到了LRU的首端,從而可能就擠掉了一個LRU上不是那麼經常被訪問,但是也會多次訪問的資料區塊。而後面的演算法,將訪問一次的資料區塊和訪問一次以上的資料區塊徹底分開,而且尋找可用資料區塊時,始終都是從輔助LRU鏈表開始掃描。實際上也就使得越傾向於只訪問一次的資料區塊越快的從記憶體中清理出去。

總結本文首先根據《Python源碼剖析》整理了Python的記憶體管理機制,重點討論了記憶體池以及對象緩衝池。在第二部分結合百度的筆試題探討了如何?一個緩衝池,重點討論了緩衝池基本架構以及資料區塊換出策略。
Further Reading and Reference:Python記憶體管理:
1. 《Python源碼剖析》 非常好的一本介紹Python源碼的書籍,裡面介紹了Python的對象機制、虛擬機器、類、記憶體管理、記憶體回收等各個方面2. http://blog.donews.com/lemur/?s=Python%E6%BA%90%E7%A0%81%E5%89%96%E6%9E%90Python Python源碼剖析的作者的部落格,專門介紹CPython源碼的專欄。3. http://docs.python.org/library/index.html  想瞭解Python? 官方文檔才是王道!
緩衝池相關:
1. http://space.itpub.net/9842/viewspace-399665 緩衝池在資料庫用的還是比較多。本文就Oracle中的Buffer cache展開了分析,有理論有樣本,非常不錯的文章,本文也引用了這篇文章的相關內容。
相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.