哈喽大家好,我是咸鱼

当谈到编程效率和性能优化时,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. 一天吃透Spring面试八股文

    内容摘自我的学习网站:topjavaer.cn Spring是一个轻量级的开源开发框架,主要用于管理 Java 应用程序中的组件和对象,并提供各种服务,如事务管理.安全控制.面向切面编程和远程访问等. ...

  2. 小技巧 | 使用 mv 重命名文件无需两次键入文件名称

    使用过 Bash 的童鞋都知道 mv 是一个可以用于文件改名的命令,而且使用这个命令修改文件名时我们需要输入两次文件名(旧名字和新名字). 如果有一种情况是只需要你改动文件名中的一个字母,而文件名又特 ...

  3. PE 结构的三种地址

    三种地址   (一)VA(虚拟地址):PE 文件被 Windows 加载到内存后的地址.   (二) RVA(相对虚拟地址):PE 文件虚拟地址相对于映射基地址(对于 EXE 文件来说,映射基地址是 ...

  4. CMU15445 (Fall 2020) 数据库系统 Project#2 - B+ Tree 详解(上篇)

    前言 考虑到 B+ 树较为复杂,CMU15-445 将 B+ 树实验拆成了两部分,这篇博客将介绍 Checkpoint#1 部分的实现过程,搭配教材 <DataBase System Conce ...

  5. docker-compose单服务器部署ELK

    docker-compose 部署ELK 本项目采用的ELK版本为6.5.3,7.0+ 以上的版本部分配置不适合,请查看docker-compose多服务器部署ELK文章 目录结构 elk |--do ...

  6. [渗透测试]—4.2 Web应用安全漏洞

    在本节中,我们将学习OWASP(开放网络应用安全项目)发布的十大Web应用安全漏洞.OWASP十大安全漏洞是对Web应用安全风险进行评估的标准,帮助开发者和安全工程师了解并防范常见的安全威胁. 1. ...

  7. 公网环境部署zabbix5.0

    实验环境 虚拟机两台,一台公网地址为 1.1.1.1,部署 zabbix server,一台公网地址为 1.1.1.2,部署 zabbix proxy,系统为centos7.2. 1 zabbix s ...

  8. MASABlazor在移动端点击保持出现悬停样式

    提出问题 最近在学习MAUIBlazor,用的MASA Blazor,发现在移动端(触屏设备)上,点击会一直显示悬停样式,如下图所示,简单研究了一下,发现这是移动端的通病. 解决问题 MASABlaz ...

  9. failed (2: No such file or directory) in /var/www/QQ_Music/nginx.conf:18

    错误原因 解决方案 引入文件 /www/server/nginx/conf/mime.types;

  10. 新一代开源流数据湖平台Apache Paimon入门实操-上

    @ 目录 概述 定义 核心功能 适用场景 架构原理 总体架构 统一存储 基本概念 文件布局 部署 环境准备 环境部署 实战 Catalog 文件系统 Hive Catalog 创建表 创建Catalo ...