當談到程式設計效率和性能最佳化時,Python 常常被調侃為「慢如蝸牛」
有趣的是,Python 程式碼在函數中運行往往比在全局範圍內運行要快得多
小夥伴們可能會有這個疑問:為什麼在函數中運行的 Python 程式碼速度更快?
今天這篇文章將會解答大家心中的疑惑
原文連結:https://stackabuse.com/why-does-python-code-run-faster-in-a-function/
譯文
要理解為什麼 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
函數要比你自己編寫函數速度更快