为什么 Python 代码在函数中运行得更快?
哈喽大家好,我是咸鱼
当谈到编程效率和性能优化时,Python 常常被调侃为“慢如蜗牛”
有趣的是,Python 代码在函数中运行往往比在全局范围内运行要快得多
小伙伴们可能会有这个疑问:为什么在函数中运行的 Python 代码速度更快?
今天这篇文章将会解答大家心中的疑惑
原文链接:https://stackabuse.com/why-does-python-code-run-faster-in-a-function/
译文
要理解为什么 Python 代码在函数中运行得更快,我们需要首先了解 Python 是如何执行代码的
我们知道,python 是一种解释型语言,它会逐行读取并执行代码
当运行一个 python 程序的时候,首先将代码编译成字节码(一种更接近机器码的中间语言)然后 python 解释器执行字节码
def hello_world():
print("Hello, World!")
import dis
dis.dis(hello_world)
#结果
2 0 LOAD_GLOBAL 0 (print)
2 LOAD_CONST 1 ('Hello, World!')
4 CALL_FUNCTION 1
6 POP_TOP
8 LOAD_CONST 0 (None)
10 RETURN_VALUE
由上所示,python 中的 dis 模块将函数 hello_world 分解为字节码
需要注意的是,python 解释器是一个执行字节码的虚拟机,默认的 python 解释器是用 C 编写的,即 CPython
还有其他的 python 解释器如 Jython(用 Java 编写),IronPython(用于 .net)和PyPy(用 Python 和 C 编写)
为什么 Python 代码在函数中运行得更快
我们来编写一个简单的例子:定义一个函数 my_function,函数内部包含一个 for 循环
def my_function():
for i in range(100000000):
pass
编译该函数的时候,字节码可能如下所示
SETUP_LOOP 20 (to 23)
LOAD_GLOBAL 0 (range)
LOAD_CONST 3 (100000000)
CALL_FUNCTION 1
GET_ITER
FOR_ITER 6 (to 22)
STORE_FAST 0 (i)
JUMP_ABSOLUTE 13
POP_BLOCK
LOAD_CONST 0 (None)
RETURN_VALUE
这里的关键指令是 STORE_FAST ,用于存储循环变量 i
现在我们把这个 for 循环放在 python 脚本的顶层(全局范围内),然后再来看一下字节码
for i in range(100000000):
pass
SETUP_LOOP 20 (to 23)
LOAD_NAME 0 (range)
LOAD_CONST 3 (100000000)
CALL_FUNCTION 1
GET_ITER
FOR_ITER 6 (to 22)
STORE_NAME 1 (i)
JUMP_ABSOLUTE 13
POP_BLOCK
LOAD_CONST 2 (None)
RETURN_VALUE
可以看到关键指令变成了 STORE_NAME,而不是 STORE_FAST
字节码 STORE_FAST比 STORE_NAME 快,因为在函数中,局部变量存储在固定长度的数组中,而不是存储在字典中。这个数组可以通过索引直接访问,使得变量检索非常快
基本上,它只是一个指向列表的指针,并增加了 PyObject 的引用计数,这两个都是高效的操作
另一方面,全局变量存储在一个字典。当访问全局变量时,Python 必须执行哈希表查找,这涉及计算哈希值,然后检索与之关联的值
虽然经过优化,但仍然比基于索引的查找慢
基准测试验证
我们知道在 Python 中,代码执行的速度取决于代码执行的位置——在函数中还是在全局作用域中
让我们用一个简单的基准测试的例子来比较一下
首先定义一个求阶乘的函数
def factorial(n):
result = 1
for i in range(1, n + 1):
result *= i
return result
然后在全局范围内执行相同的代码
n = 20
result = 1
for i in range(1, n + 1):
result *= i
为了对这两段代码进行基准测试,我们可以在 Python 中使用 timeit 模块,它提供了一种简单的方法来对少量 Python 代码进行计时
import timeit
# 函数
def benchmark():
start = timeit.default_timer()
factorial(20)
end = timeit.default_timer()
print(end - start)
benchmark()
# Prints: 3.541994374245405e-06
# 全局范围
start = timeit.default_timer()
n = 20
result = 1
for i in range(1, n + 1):
result *= i
end = timeit.default_timer()
print(end - start)
# Pirnts: 5.375011824071407e-06
可以看到,函数代码的执行速度比全局作用域代码要快
需要注意的是,这两段代码最好不要放在同一脚本中,要分开单独运行
这是因为
benchmark()函数在执行时间上增加了一些开销,并且全局代码在内部进行了优化
cProfile 分析
python 提供了一个 cProfile 内置模块
让我们用它来分析一个新例子:在局部和全局范围内计算平方和
import cProfile
def sum_of_squares():
total = 0
for i in range(1, 10000000):
total += i * i
i = None
total = 0
def sum_of_squares_g():
global i
global total
for i in range(1, 10000000):
total += i * i
def profile(func):
pr = cProfile.Profile()
pr.enable()
func()
pr.disable()
pr.print_stats()
#
# Profile function code
#
print("Function scope:")
profile(sum_of_squares)
#
# Profile global scope code
#
print("Global scope:")
profile(sum_of_squares_g)
上面的例子中,可以认为sum_of_squares_g() 函数是全局的,因为它使用了两个全局变量, i 和 total
从性能分析结果中,可以看到函数代码在执行时间方面比全局更有效
Function scope:
2 function calls in 0.903 seconds
Ordered by: standard name
ncalls tottime percall cumtime percall filename:lineno(function)
1 0.903 0.903 0.903 0.903 profiler.py:3(sum_of_squares)
1 0.000 0.000 0.000 0.000 {method 'disable' of '_lsprof.Profiler' objects}
Global scope:
2 function calls in 1.358 seconds
Ordered by: standard name
ncalls tottime percall cumtime percall filename:lineno(function)
1 1.358 1.358 1.358 1.358 profiler.py:10(sum_of_squares_g)
1 0.000 0.000 0.000 0.000 {method 'disable' of '_lsprof.Profiler' objects}
如何优化 python 函数的性能
前面我们知道,Python 代码在函数中运行往往比在全局范围内运行要快得多
如果想要进一步提高 python 函数代码效率,不妨考虑一下使用局部变量而不是全局变量
另一种方法是尽可能使用内置函数和库。Python 的内置函数是用 C 实现的,比 Python 快得多
比如 NumPy 和 Pandas,也是用 C 或 C++ 实现的,它们比实现同样功能的 Python 代码速度更快
又比如同样是实现数字求和的功能,python 内置的 sum 函数要比你自己编写函数速度更快
为什么 Python 代码在函数中运行得更快?的更多相关文章
- 使用QFileInfo类获取文件信息(在NTFS文件系统上,出于性能考虑,文件的所有权和权限检查在默认情况下是被禁用的,通过qt_ntfs_permission_lookup开启和操作。absolutePath()必须查询文件系统。而path()函数,可以直接作用于文件名本身,所以,path() 函数的运行会更快)
版权声明:本文为博主原创文章,未经博主允许不得转载. https://blog.csdn.net/Amnes1a/article/details/65444966QFileInfo类为我们提供了系统无 ...
- pycharm中运行成功的python代码在jenkin中运行问题总结
我们在用selenium+python完成了项目的UI自动化后,一般用jekins持续集成工具来定期运行,python程序在pycharm中编辑运行成功,但在jenkins中运行失败的两个问题,整理如 ...
- Python之在函数中使用列表作为默认参数
本文将介绍笔者在工作中遇到的Python的一个坑,那就是使用列表作为默认参数. 我们知道,在Python中,列表(list)是可变对象,所以列表的内容可能会在函数内改变.另一个需要注意的是,使 ...
- Python和多线程(multi-threading)。这是个好主意码?列举一些让Python代码以并行方式运行的方法。
Python并不支持真正意义上的多线程.Python中提供了多线程包,但是如果你想通过多线程提高代码的速度,使用多线程包并不是个好主意.Python中有一个被称为Global Interpreter ...
- python面试题之多线程好吗?列举一些让Python代码以并行方式运行的方法
答案 Python并不支持真正意义上的多线程.Python中提供了多线程包,但是如果你想通过多线程提高代码的速度,使用多线程包并不是个好主意.Python中有一个被称为Global Interpret ...
- python json-json.loads()函数中的字符串需要是严格的json串格式,不能包含单引号
先看下json的dumps()和loads()函数的定义 json.dumps():将一个Python对象编码成JSON字符串.把字典对象转换成json串 json.loads():将JSON格式字符 ...
- 解决python在命令行中运行时导入包失败,出现错误信息 "ModuleNotFoundError: No module named ***"
转自https://www.cnblogs.com/dreamyu/p/7889959.html https://www.cnblogs.com/lifeofershisui/p/8135702.ht ...
- 如何让python程序运行得更快
原则1:不优化 原则2:不要优化那些不重要的部分(否则会降低可读性) 解决方案: 1. 使用函数,局部变量比全局变量快很多.尽量使用函数,如main() 2. 有选择性的消除属性访问. 如多用 fro ...
- Python 3.12 目标:还可以更快!
按照发布计划,Python 3.11.0 将于 2022 年 10 月 24 日发布. 据测试,3.11 相比于 3.10,将会有 10-60% 的性能提升,这个成果主要归功于"Faster ...
- SharePoint 2010中使用SPListItemCollectionPosition更快的结果
转:http://www.16kan.com/article/detail/318657.html Introduction介绍 In this article we will explore the ...
随机推荐
- 配置VsCode的QT工程
配置VsCode的QT工程 VsCode + qmake 环境(Environment): Windows11 Qt5.12.11+ MinGW64 编译套件 VsCode (version = 1. ...
- 适用于Linux命令的10个R函数
由于微信不允许外部链接,你需要点击文章尾部左下角的 "阅读原文",才能访问文中链接. 这篇文章将介绍 10 个不同的 Linux 命令及其 R 实现方法. 如果您有兴趣学习更多 R ...
- Nginx 反向代理的配置和注意点(成功配置)
反向代理配置成功 首先,Nginx 和 Java 后端都运行在云服务器的 docker 容器中.ps: 需要确保云服务器端口正常开放,以及两个容器都能被正常的访问. 现在想让 ng 做反向代理达到如下 ...
- rust cargo build一直出现 Blocking waiting for file lock on package cache
如果确定没有多个程序占用,可以删除rm -rf ~/.cargo/.package-cache,然后再执行
- JVM中的-Xms 、-Xmx 参数该如何设置
在 Java 虚拟机(JVM)中,-Xms 和 -Xmx 都是用来设置 JVM 堆内存大小的参数.其中,-Xms 用于设置 JVM 启动时分配的初始堆内存大小,而 -Xmx 用于设置 JVM 堆内存的 ...
- expected unqualified-id on oneapi tbb12
230628-expected unqualified-id eton@230628 在编译supra基于debian12 Qt5.15 tbb12 libtbb12/stable,now 2021. ...
- XHbuilder 需要的 ipa 签名,超详细的教程,你不看吃亏的是自己!
今天使用 hbuilder 运行到 ios 真机的时候,突然发现还需要 ipa 签名,这是什么东东呢? 1.IPA 签名是什么? 因苹果公司禁止企业证书用于非企业内部开发者.所以开发者无法再使用DCl ...
- PostgreSQL JDBC 开发指导
JDBC 驱动程序 目录 设置 JDBC 驱动程序 初始化驱动程序 使用 SSL 发出查询和处理结果 调用存储函数和过程 存储二进制数据 JDBC 转义 PostgreSQL 扩展的 JDBC API ...
- Mysql基础篇(二)之函数和约束
一. 函数 Mysql中的函数主要分为四类:字符串函数.数值函数.日期函数.流程函数 1. 字符串函数 常用函数如下: 函数 功能 CONCAT(S1, S2, ......Sn) 字符串拼接,将S1 ...
- 个人博客迁移到托管平台Netlify
Netlify是一家国外的静态网站的托管平台,提供免费的https,自动化部署和升级,可以监控GitHub.GitLab或者Bitbucket做到自动更新发布. 个人体会访问速度不是很理想,不如部署在 ...