哈喽大家好,我是咸鱼

当谈到编程效率和性能优化时,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_FASTSTORE_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() 函数是全局的,因为它使用了两个全局变量, itotal

从性能分析结果中,可以看到函数代码在执行时间方面比全局更有效

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 代码在函数中运行得更快?的更多相关文章

  1. 使用QFileInfo类获取文件信息(在NTFS文件系统上,出于性能考虑,文件的所有权和权限检查在默认情况下是被禁用的,通过qt_ntfs_permission_lookup开启和操作。absolutePath()必须查询文件系统。而path()函数,可以直接作用于文件名本身,所以,path() 函数的运行会更快)

    版权声明:本文为博主原创文章,未经博主允许不得转载. https://blog.csdn.net/Amnes1a/article/details/65444966QFileInfo类为我们提供了系统无 ...

  2. pycharm中运行成功的python代码在jenkin中运行问题总结

    我们在用selenium+python完成了项目的UI自动化后,一般用jekins持续集成工具来定期运行,python程序在pycharm中编辑运行成功,但在jenkins中运行失败的两个问题,整理如 ...

  3. Python之在函数中使用列表作为默认参数

      本文将介绍笔者在工作中遇到的Python的一个坑,那就是使用列表作为默认参数.   我们知道,在Python中,列表(list)是可变对象,所以列表的内容可能会在函数内改变.另一个需要注意的是,使 ...

  4. Python和多线程(multi-threading)。这是个好主意码?列举一些让Python代码以并行方式运行的方法。

    Python并不支持真正意义上的多线程.Python中提供了多线程包,但是如果你想通过多线程提高代码的速度,使用多线程包并不是个好主意.Python中有一个被称为Global Interpreter ...

  5. python面试题之多线程好吗?列举一些让Python代码以并行方式运行的方法

    答案 Python并不支持真正意义上的多线程.Python中提供了多线程包,但是如果你想通过多线程提高代码的速度,使用多线程包并不是个好主意.Python中有一个被称为Global Interpret ...

  6. python json-json.loads()函数中的字符串需要是严格的json串格式,不能包含单引号

    先看下json的dumps()和loads()函数的定义 json.dumps():将一个Python对象编码成JSON字符串.把字典对象转换成json串 json.loads():将JSON格式字符 ...

  7. 解决python在命令行中运行时导入包失败,出现错误信息 "ModuleNotFoundError: No module named ***"

    转自https://www.cnblogs.com/dreamyu/p/7889959.html https://www.cnblogs.com/lifeofershisui/p/8135702.ht ...

  8. 如何让python程序运行得更快

    原则1:不优化 原则2:不要优化那些不重要的部分(否则会降低可读性) 解决方案: 1. 使用函数,局部变量比全局变量快很多.尽量使用函数,如main() 2. 有选择性的消除属性访问. 如多用 fro ...

  9. Python 3.12 目标:还可以更快!

    按照发布计划,Python 3.11.0 将于 2022 年 10 月 24 日发布. 据测试,3.11 相比于 3.10,将会有 10-60% 的性能提升,这个成果主要归功于"Faster ...

  10. SharePoint 2010中使用SPListItemCollectionPosition更快的结果

    转:http://www.16kan.com/article/detail/318657.html Introduction介绍 In this article we will explore the ...

随机推荐

  1. 配置VsCode的QT工程

    配置VsCode的QT工程 VsCode + qmake 环境(Environment): Windows11 Qt5.12.11+ MinGW64 编译套件 VsCode (version = 1. ...

  2. 适用于Linux命令的10个R函数

    由于微信不允许外部链接,你需要点击文章尾部左下角的 "阅读原文",才能访问文中链接. 这篇文章将介绍 10 个不同的 Linux 命令及其 R 实现方法. 如果您有兴趣学习更多 R ...

  3. Nginx 反向代理的配置和注意点(成功配置)

    反向代理配置成功 首先,Nginx 和 Java 后端都运行在云服务器的 docker 容器中.ps: 需要确保云服务器端口正常开放,以及两个容器都能被正常的访问. 现在想让 ng 做反向代理达到如下 ...

  4. rust cargo build一直出现 Blocking waiting for file lock on package cache

    如果确定没有多个程序占用,可以删除rm -rf ~/.cargo/.package-cache,然后再执行

  5. JVM中的-Xms 、-Xmx 参数该如何设置

    在 Java 虚拟机(JVM)中,-Xms 和 -Xmx 都是用来设置 JVM 堆内存大小的参数.其中,-Xms 用于设置 JVM 启动时分配的初始堆内存大小,而 -Xmx 用于设置 JVM 堆内存的 ...

  6. expected unqualified-id on oneapi tbb12

    230628-expected unqualified-id eton@230628 在编译supra基于debian12 Qt5.15 tbb12 libtbb12/stable,now 2021. ...

  7. XHbuilder 需要的 ipa 签名,超详细的教程,你不看吃亏的是自己!

    今天使用 hbuilder 运行到 ios 真机的时候,突然发现还需要 ipa 签名,这是什么东东呢? 1.IPA 签名是什么? 因苹果公司禁止企业证书用于非企业内部开发者.所以开发者无法再使用DCl ...

  8. PostgreSQL JDBC 开发指导

    JDBC 驱动程序 目录 设置 JDBC 驱动程序 初始化驱动程序 使用 SSL 发出查询和处理结果 调用存储函数和过程 存储二进制数据 JDBC 转义 PostgreSQL 扩展的 JDBC API ...

  9. Mysql基础篇(二)之函数和约束

    一. 函数 Mysql中的函数主要分为四类:字符串函数.数值函数.日期函数.流程函数 1. 字符串函数 常用函数如下: 函数 功能 CONCAT(S1, S2, ......Sn) 字符串拼接,将S1 ...

  10. 个人博客迁移到托管平台Netlify

    Netlify是一家国外的静态网站的托管平台,提供免费的https,自动化部署和升级,可以监控GitHub.GitLab或者Bitbucket做到自动更新发布. 个人体会访问速度不是很理想,不如部署在 ...