要理解为什么 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 函数要比你自己编写函数速度更快