在程式設計中,創建物件模組主要是透過生成物件來實現。當物件使用結束後,則會成為不再需要的模組進行銷毀。
而在系統進行物件的生成與銷毀過程中會大量的增加記憶體的消耗,同時物件的銷毀往往會留下殘留的信息,這樣將會伴隨記憶體外洩的問題存在。
在實際的程式開發過程中,往往需要產生和銷毀大量重複的對象,這就使得記憶體洩漏產生的資訊過多而無法被系統回收,從而佔用系統更多的內存,而且生成物體過多時無法確定被什麼模組實例化實現,對系統造成負擔,不利於管理及後續操作,長此以往最終將導致程式變慢甚至崩潰。
物件池是存放了一批已經建立好的物件的池,它是一個用來維護物件的結構。當程式需要使用物件的時候,可以直接從池中取得該對象,而不是實例化一個新的物件。
在程式設計過程中,大部分人關注的往往只是物件的使用和效果的實現,實際上創建和使用之間還有一個初始化的過程,不過系統會將初始化和創建這兩步驟結合在了一起,這使得設計者忽略了系統創建和銷毀物件過程對系統的影響。
通常來講,一個物件的創建和銷毀過程開銷很小,可以忽略不計,但是如果一個程式中涉及到一種物件多次創建,並且創建時間比較長,那就會能很明顯的感覺到這部分的消耗所造成的系統速度受限。
物件池可以看作是減少 GC 壓力的首選方法,同時也是最簡單的方法。
Pond 是一個 Python 中高效率的通用物件池,具有效能好、記憶體佔用小、命中率高的特性。基於近似統計的根據頻率自動回收的能力,能夠自動調整每個物件池的空閒物件數量。
因為目前 Python 目前沒有比較好的、測試使用案例完整、程式碼註解完整、文件完善的物件池化函式庫,同時目前的主流物件庫也沒有比較智慧的自動回收機制。
Pond 可能是 Python 中第一個社群公開的測試案例完整,覆蓋率 90% 以上、程式碼註解完備、文件完善的物件池化函式庫。
Pond 靈感來自 Apache Commons Pool、Netty Recycler、HikariCP、Caffeine,集合了多家的優點。
其次 Pond 透過使用近似計數的方式以極小的記憶體空間統計每個物件池的使用頻率,並且自動回收。
流量較隨機平均的情況下,預設策略和權重可以降低 48.85% 記憶體佔用,借取命中率 100%。
流量較為符合 2/8 定律的情況下,預設策略和權重可以降低 45.7% 記憶體佔用, 借取命中率 100%。
Pond 主要由 FactoryDict、Counter、PooledObjectTree 三部分以及一個單獨的回收執行緒構成。
使用 Pond 需要實作物件工廠 PooledObjectFactory,PooledObjectFactory 提供物件的建立、初始化、銷毀、驗證等操作,由 Pond 呼叫。
所以為了讓物件池支援存放完全不同的對象,Pond 使用了一個字典來記錄每個工廠類別的名稱和自己實現的工廠類別的實例化物件。
每個 PooledObjectFactory 應該具備建立物件、銷毀物件、驗證物件是否還可用、重置物件四個功能。
比較特別的是Pond 支援自動重置對象,因為某些場景下可能會存在對像中要先賦值進行傳遞,傳遞完又被回收的情況,為了避免污染建議這種場景下無比實現這個功能。
Counter 中儲存了一個近似計數器。
PooleedObjectTree 是個字典,每個 key 對應一個先進先出的佇列,這些佇列都是執行緒安全的。
每個佇列中保存著多個 PooleedObject。 PooledObejct 保存了建立時間、最後借出的時間以及實際需要的物件。
Pond 的借用和回收都是線程安全的。 Python 的 queue 模組提供了一個適用於多執行緒程式設計的先進先出(FIFO)資料結構。它可以用來安全地在生產者和消費者線程之間傳遞訊息或其他資料。
鎖定是呼叫者來處理的,所有多個執行緒能夠安全且容易的使用相同的 Queue 實例工作。而 Pond 的借用和回收都是在操作 queue,所以基本上可以認為是線程安全的。
在使用Pond 借出一個物件時,會先檢查想要藉用的物件的種類是否已經在PooledObjectTree 存在,如果存在會檢查這個物件的物件池是否為空,如果為空會建立一個新的。
如果物件池中有多餘的對象,會利用 queue 彈出一個物件並驗證這個物件是否可用。如果不可用會自動呼叫所屬的 Factory 清理銷毀該對象,同時清理它在 Python 中的 GC 計數,讓它更快被 GC 回收,不斷拿取下一個直至有可用的。
如果這個物件可用,則會直接傳回。當然無論是從對像池中取出對象還是新創建了一個對象,都會利用 Counter 增加一個計數。
回收一個物件時會判斷目標物件池存不存在,如果存在會檢查物件池是否已經滿了,滿了的話會自動銷毀要歸還的這個物件。
然後會檢查這個物件是否已經被借出太長時間,如果超過了配置的最長時間同樣會被清理掉。
自動回收時每隔一段時間,預設是 300 s,就會執行一次。自動清理不經常使用的物件池中的物件。
你可以先安裝 Pond 的函式庫並且在你的專案中引用。
pip install pondpond
from pond import Pond, PooledObjectFactory, PooledObject
首先你需要聲明一個你想要放入的類型的物件的工廠類,例如下面的例子我們希望池化的物件是Dog,所以我們先聲明一個PooledDogFactory 類,並且實作PooledObjectFactory。
class Dog: name: str validate_result:bool = True class PooledDogFactory(PooledObjectFactory): def creatInstantce(self) -> PooledObject: dog = Dog() dog.name = "puppy" return PooledObject(dog) def destroy(self, pooled_object: PooledObject): del pooled_object def reset(self, pooled_object: PooledObject) -> PooledObject: pooled_object.keeped_object.name = "puppy" return pooled_object def validate(self, pooled_object: PooledObject) -> bool: return pooled_object.keeped_object.validate_result
接著你需要建立Pond 的物件:
pond = Pond(borrowed_timeout=2, time_between_eviction_runs=-1, thread_daemon=True, eviction_weight=0.8)
Pond 可以傳遞一些參數進去,分別代表:
borrowed_timeout :單位為秒,借出對象的最長期限,超過期限的物件歸還時會自動銷毀不會放入物件池。
time_between_eviction_runs :單位為秒,自動回收的間隔時間。
thread_daemon :守護線程,如果為 True,自動回收的線程會隨著主線程關閉而關閉。
eviction_weight :自動回收時權重,會將這個權重與最大使用頻次想乘,使用頻次小於這個值的物件池中的物件都會進入清理步驟。
實例化工廠類別:
factory = PooledDogFactory(pooled_maxsize=10, least_one=False)
所有繼承了 PooledObjectFactory 都會自帶建構函數,可以傳遞 pooled_maxsize 和 least_one 兩個參數。
pooled_maxsize:這個工廠類別產生的物件的物件池的最大能放置的數量。
least_one:如果為 True,在進入自動清理時,這個工廠類別產生的物件的物件池會至少保留一個物件。
向Pond 註冊這個工廠對象,預設會使用factory 的類別名稱作為PooledObjectTree 的key :
pond.register(factory)
當然你也可以自訂它的名字,名字會作為PooledObjectTree 的key:
pond.register(factory, name="PuppyFactory")
註冊成功後,Pond 會自動根據factory 中設定的pooled_maxsize 自動開始建立物件直到填滿這個物件池。
借用和歸還物件:
pooled_object: PooledObject = pond.borrow(factory) dog: Dog = pooled_object.use() pond.recycle(pooled_object, factory)
當然你可以用名字來進行借用和歸還:
pooled_object: PooledObject = pond.borrow(name="PuppyFactory") dog: Dog = pooled_object.use() pond.recycle(pooled_object, name="PuppyFactory")
完全清理一個物件池:
pond.clear(factory)
透過名字清理一個物件池:
pond.clear(name="PuppyFactory")
正常情況下,你只需要使用上面的這些方法,生成物件和回收物件都是全自動的。
以上是最新開源:高效能的 Python 通用物件池化函式庫的詳細內容。更多資訊請關注PHP中文網其他相關文章!