为什么 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 ...
随机推荐
- 一天吃透Spring面试八股文
内容摘自我的学习网站:topjavaer.cn Spring是一个轻量级的开源开发框架,主要用于管理 Java 应用程序中的组件和对象,并提供各种服务,如事务管理.安全控制.面向切面编程和远程访问等. ...
- 小技巧 | 使用 mv 重命名文件无需两次键入文件名称
使用过 Bash 的童鞋都知道 mv 是一个可以用于文件改名的命令,而且使用这个命令修改文件名时我们需要输入两次文件名(旧名字和新名字). 如果有一种情况是只需要你改动文件名中的一个字母,而文件名又特 ...
- PE 结构的三种地址
三种地址 (一)VA(虚拟地址):PE 文件被 Windows 加载到内存后的地址. (二) RVA(相对虚拟地址):PE 文件虚拟地址相对于映射基地址(对于 EXE 文件来说,映射基地址是 ...
- CMU15445 (Fall 2020) 数据库系统 Project#2 - B+ Tree 详解(上篇)
前言 考虑到 B+ 树较为复杂,CMU15-445 将 B+ 树实验拆成了两部分,这篇博客将介绍 Checkpoint#1 部分的实现过程,搭配教材 <DataBase System Conce ...
- docker-compose单服务器部署ELK
docker-compose 部署ELK 本项目采用的ELK版本为6.5.3,7.0+ 以上的版本部分配置不适合,请查看docker-compose多服务器部署ELK文章 目录结构 elk |--do ...
- [渗透测试]—4.2 Web应用安全漏洞
在本节中,我们将学习OWASP(开放网络应用安全项目)发布的十大Web应用安全漏洞.OWASP十大安全漏洞是对Web应用安全风险进行评估的标准,帮助开发者和安全工程师了解并防范常见的安全威胁. 1. ...
- 公网环境部署zabbix5.0
实验环境 虚拟机两台,一台公网地址为 1.1.1.1,部署 zabbix server,一台公网地址为 1.1.1.2,部署 zabbix proxy,系统为centos7.2. 1 zabbix s ...
- MASABlazor在移动端点击保持出现悬停样式
提出问题 最近在学习MAUIBlazor,用的MASA Blazor,发现在移动端(触屏设备)上,点击会一直显示悬停样式,如下图所示,简单研究了一下,发现这是移动端的通病. 解决问题 MASABlaz ...
- failed (2: No such file or directory) in /var/www/QQ_Music/nginx.conf:18
错误原因 解决方案 引入文件 /www/server/nginx/conf/mime.types;
- 新一代开源流数据湖平台Apache Paimon入门实操-上
@ 目录 概述 定义 核心功能 适用场景 架构原理 总体架构 统一存储 基本概念 文件布局 部署 环境准备 环境部署 实战 Catalog 文件系统 Hive Catalog 创建表 创建Catalo ...