Python性能調(diào)優(yōu):copy模塊中的deepcopy函數(shù)
????最近在寫的一些Python小項目中,里面用到了遺傳算法這種需要大量計算的算法(CPU密集型任務(wù)),因為GIL鎖的存在,Python中的多線程在對CPU密集型任務(wù)的處理中顯得有些雞肋,于是嘗試采用multiprocessing(多進(jìn)程)模塊來就行計算任務(wù)處理,但提升有限。

關(guān)于GIL
????GIL(Global Interpreter Lock)是Python解釋器中的一個全局鎖,用于確保在任何時刻只有一個線程在執(zhí)行Python字節(jié)碼,一定程度上避免了冒險-競爭問題。這意味著,盡管Python支持多線程編程,但在任何時刻只有一個線程可以在解釋器中執(zhí)行Python代碼。如果有多個線程需要同時執(zhí)行Python代碼,則它們必須在GIL上進(jìn)行競爭,獲得GIL的線程可以運行Python代碼,而其他線程則必須等待GIL。

問題查找
????做性能優(yōu)化時,首先要找出性能的瓶頸在什么地方,才能夠做針對性的優(yōu)化。Python 的性能剖析主要有下面幾種方法:
cProfile
line_profiler
pyflame
pyinstrument
????在本文章中將使用Python內(nèi)置的cProfile模塊,cProfile是Python標(biāo)準(zhǔn)庫中的一個性能分析工具,用于分析Python程序的性能瓶頸。它可以統(tǒng)計程序中每個函數(shù)的調(diào)用次數(shù)、運行時間以及占用的內(nèi)存等信息,并將這些信息以一定格式打印出來。
????與Python內(nèi)置的profile模塊不同,cProfile使用了C語言實現(xiàn)的底層分析引擎,因此可以大大減少分析工具本身對程序性能的影響,同時提供更為精確的分析結(jié)果。
官方例子(命令行使用):
其中-o 選項指定生成 cProfile 的二進(jìn)制性能結(jié)果保存至指定地址。
光生成二進(jìn)制結(jié)果還不夠,這時就需要一些更直觀的方式:使用flameprof生成svg可視化火焰圖
由于是外置庫,首先使用pip進(jìn)行安裝:
假設(shè)上面使用cProfile生成的二進(jìn)制文件名為input.prof:
執(zhí)行命令即可獲得該程序的火焰圖。

可以明顯看出,時間主要用在了deepcopy這個函數(shù)上。該函數(shù)主要功能是實現(xiàn)對象的深拷貝。
查閱cpython源碼(https://github.com/python/cpython/blob/main/Lib/copy.py)發(fā)現(xiàn),這個函數(shù)會遞歸地遍歷輸入對象的所有子對象,并復(fù)制它們以創(chuàng)建一個新的獨立對象。它使用一個名為memo
的字典來跟蹤已經(jīng)被復(fù)制的對象,避免對同一個對象進(jìn)行重復(fù)的復(fù)制。關(guān)鍵就在這個memo(全名memorandum)的字典變量,該函數(shù)需要實時維護(hù)這個字典,故性能會有所下降。但在絕大多數(shù)情況下,程序里都不存在相互引用。但作為通用模塊的copy模塊必須考慮這種特殊情況。
解決方法
自定義deepcopy方法:
重新生成火焰圖:

分析上圖,時間大部分都在運行numpy計算,而不是deepcopy,而每秒迭代次數(shù)得到了300%的巨大提升。