為什么 Python 代碼在函數(shù)中運行得更快?
要理解為什么 Python 代碼在函數(shù)中運行得更快,我們需要首先了解 Python 是如何執(zhí)行代碼的
我們知道,python 是一種解釋型語言,它會逐行讀取并執(zhí)行代碼
當(dāng)運行一個 python 程序的時候,首先將代碼編譯成字節(jié)碼(一種更接近機器碼的中間語言)然后 python 解釋器執(zhí)行字節(jié)碼
圖片
圖片
由上所示,python 中的 dis 模塊將函數(shù) hello_world 分解為字節(jié)碼
需要注意的是,python 解釋器是一個執(zhí)行字節(jié)碼的虛擬機,默認(rèn)的 python 解釋器是用 C 編寫的,即 CPython
還有其他的 python 解釋器如 Jython(用 Java 編寫),IronPython(用于 .net)和PyPy(用 Python 和 C 編寫)
為什么 python 代碼在函數(shù)中運行得更快
我們來編寫一個簡單的例子:定義一個函數(shù) my_function,函數(shù)內(nèi)部包含一個 for 循環(huán)
圖片
編譯該函數(shù)的時候,字節(jié)碼可能如下所示
圖片
這里的關(guān)鍵指令是 STORE_FAST ,用于存儲循環(huán)變量 i
現(xiàn)在我們把這個 for 循環(huán)放在 python 腳本的頂層(全局范圍內(nèi)),然后再來看一下字節(jié)碼
圖片
圖片
可以看到關(guān)鍵指令變成了 STORE_NAME,而不是 STORE_FAST
字節(jié)碼 STORE_FAST比 STORE_NAME 快,因為在函數(shù)中,局部變量存儲在固定長度的數(shù)組中,而不是存儲在字典中。這個數(shù)組可以通過索引直接訪問,使得變量檢索非常快
基本上,它只是一個指向列表的指針,并增加了 PyObject 的引用計數(shù),這兩個都是高效的操作
另一方面,全局變量存儲在一個字典。當(dāng)訪問全局變量時,Python 必須執(zhí)行哈希表查找,這涉及計算哈希值,然后檢索與之關(guān)聯(lián)的值
雖然經(jīng)過優(yōu)化,但仍然比基于索引的查找慢
基準(zhǔn)測試驗證
我們知道在 Python 中,代碼執(zhí)行的速度取決于代碼執(zhí)行的位置——在函數(shù)中還是在全局作用域中
讓我們用一個簡單的基準(zhǔn)測試的例子來比較一下
首先定義一個求階乘的函數(shù)
圖片
然后在全局范圍內(nèi)執(zhí)行相同的代碼
圖片
為了對這兩段代碼進行基準(zhǔn)測試,我們可以在 Python 中使用 timeit 模塊,它提供了一種簡單的方法來對少量 Python 代碼進行計時
圖片
可以看到,函數(shù)代碼的執(zhí)行速度比全局作用域代碼要快
需要注意的是,這兩段代碼最好不要放在同一腳本中,要分開單獨運行
這是因為 benchmark() 函數(shù)在執(zhí)行時間上增加了一些開銷,并且全局代碼在內(nèi)部進行了優(yōu)化
cProfile 分析
python 提供了一個 cProfile 內(nèi)置模塊
讓我們用它來分析一個新例子:在局部和全局范圍內(nèi)計算平方和
圖片
上面的例子中,可以認(rèn)為sum_of_squares_g() 函數(shù)是全局的,因為它使用了兩個全局變量, i 和 total
從性能分析結(jié)果中,可以看到函數(shù)代碼在執(zhí)行時間方面比全局更有效
圖片
如何優(yōu)化 python 函數(shù)的性能
前面我們知道,Python 代碼在函數(shù)中運行往往比在全局范圍內(nèi)運行要快得多
如果想要進一步提高 python 函數(shù)代碼效率,不妨考慮一下使用局部變量而不是全局變量
另一種方法是盡可能使用內(nèi)置函數(shù)和庫。Python 的內(nèi)置函數(shù)是用 C 實現(xiàn)的,比 Python 快得多
比如 NumPy 和 Pandas,也是用 C 或 C++ 實現(xiàn)的,它們比實現(xiàn)同樣功能的 Python 代碼速度更快
又比如同樣是實現(xiàn)數(shù)字求和的功能,python 內(nèi)置的 sum 函數(shù)要比你自己編寫函數(shù)速度更快















 
 
 








 
 
 
 