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