加速 Python for 循环(python加快循环速度)
off999 2024-10-23 12:50 20 浏览 0 评论
在接下来的内容中,我会给大家分享一些简便的方式,能够让 Python for 循环的速度实现 1.3 至 900 倍的提升。
Python 自身所具备的一个常用功能是 timeit 模块。在后续的几个部分中,我们会借助它来衡量循环当前的性能表现以及改进之后的性能状况。
对于每一种方法,我们都通过运行测试来设定基线,该测试涵盖了在 10 次测试运行中对被测函数执行 100K 次(循环),接着计算出每个循环的平均时间(以纳秒为单位,ns)。
几个简便方法
1、列表推导式
基线版本(低效方式)
计算数字的幂
未使用列表推导式
def test_01_v0(numbers):
output = []
for n in numbers:
output.append(n ** 2.5)
return output
改进版本
(使用列表推导式)
def test_01_v1(numbers):
output = [n ** 2.5 for n in numbers]
return output
测试结果如下:
测试结果汇总
基线:32.158 ns 每个循环
改进:16.040 ns 每个循环
% 改进:50.1 %
加速:2.00x
能够看到,运用列表推导式能够实现 2 倍速的提升。
2、在外部计算长度
倘若需要依据列表的长度进行迭代,那么就在 for 循环之外进行计算。
基线版本(低效方式)
(在 for 循环内部计算长度)
def test_02_v0(numbers):
output_list = []
for i in range(len(numbers)):
output_list.append(i * 2)
return output_list
改进版本
(在 for 循环外部计算长度)
def test_02_v1(numbers):
my_list_length = len(numbers)
output_list = []
for i in range(my_list_length):
output_list.append(i * 2)
return output_list
通过将列表长度的计算移出 for 循环,实现了 1.6 倍的加速,或许这个方法鲜为人知。
测试结果汇总
基线:112.135 ns 每个循环
改进:68.304 ns 每个循环
% 改进:39.1 %
加速:1.64x
3、使用 Set
在通过 for 循环进行比较的情形下使用 set。
使用 for 循环进行嵌套查找
def test_03_v0(list_1, list_2):
基线版本(低效方式)
(使用 for 循环进行嵌套查找)
common_items = []
for item in list_1:
if item in list_2:
common_items.append(item)
return common_items
def test_03_v1(list_1, list_2):
改进版本
(使用 set 替代嵌套查找)
s_1 = set(list_1)
s_2 = set(list_2)
output_list = []
common_items = s_1.intersection(s_2)
return common_items
在使用嵌套 for 循环进行比较的情况下,使用 set 能够实现 498 倍的加速。
测试结果汇总
基线:9047.078 ns 每个循环
改进: 18.161 ns 每个循环
% 改进:99.8 %
加速:498.17x
4、跳过不相关的迭代
避免冗余计算,也就是跳过不相关的迭代。
低效代码示例,用于在数字列表中查找第一个偶数平方
def function_do_something(numbers):
for n in numbers:
square = n * n
if square % 2 == 0:
return square
return None # 未找到偶数平方
改进后的代码,在查找结果时避免冗余计算
def function_do_something_v1(numbers):
even_numbers = [i for n in numbers if n%2==0]
for n in even_numbers:
square = n * n
return square
return None # 未找到偶数平方
这个方法需要在设计 for 循环的内容时进行代码规划,具体能够提升的幅度可能因实际情况而有所不同:
测试结果汇总
基线:16.912 ns 每个循环
改进:8.697 ns 每个循环
% 改进:48.6 %
加速:1.94x
5、代码合并
在某些情形下,直接把简单函数的代码融入到循环中,能够提高代码的紧凑性和执行速度。
低效代码示例
循环中多次调用 is_prime 函数
def is_prime(n):
if n <= 1:
return False
for i in range(2, int(n**0.5) + 1):
if n % i == 0:
return False
return True
def test_05_v0(n):
基线版本(低效方式)
(多次调用 is_prime 函数)
count = 0
for i in range(2, n + 1):
if is_prime(i):
count += 1
return count
def test_05_v1(n):
改进版本
(将 is_prime 函数的逻辑内联)
count = 0
for i in range(2, n + 1):
if i <= 1:
continue
for j in range(2, int(i**0.5) + 1):
if i % j == 0:
break
else:
count += 1
return count
这样也能够实现 1.3 倍的提升。
测试结果汇总
基线:1271.188 ns 每个循环
改进:939.603 ns 每个循环
% 改进:26.1 %
加速:1.35x
这是为何呢?
调用函数会产生开销,例如在堆栈上推入和弹出变量、进行函数查找以及参数传递。当一个简单的函数在循环中被反复调用时,函数调用的开销会增加并影响性能。所以将函数的代码直接嵌入到循环中能够消除这种开销,从而有可能显著提升速度。
??但在此需要注意,平衡代码的可读性和函数调用的频率是一个需要考虑的问题。
一些小技巧
6. 避免重复
思考避免重复计算,其中有些计算可能是多余的,并且会拖慢代码的速度。相反,在适用的情况下考虑预计算。
def test_07_v0(n):
低效代码示例
嵌套循环中的重复计算
result = 0
for i in range(n):
for j in range(n):
result += i * j
return result
def test_07_v1(n):
改进代码示例
利用预计算值来加速
pv = [[i * j for j in range(n)] for i in range(n)]
result = 0
for i in range(n):
result += sum(pv[i][:i+1])
return result
结果如下
测试结果汇总
基线:139.146 ns 每个循环
改进:92.325 ns 每个循环
% 改进:33.6 %
加速:1.51x
7、使用 Generators
生成器支持延迟求值,也就是说,只有当向它请求下一个值时,内部的表达式才会被计算,动态处理数据有助于减少内存使用并提升性能。尤其是在处理大型数据集时
def test_08_v0(n):
基线版本(低效方式)
(低效地计算第 n 个斐波那契数,使用列表)
if n <= 1:
return n
f_list = [0, 1]
for i in range(2, n + 1):
f_list.append(f_list[i - 1] + f_list[i - 2])
return f_list[n]
def test_08_v1(n):
改进版本
(高效地计算第 n 个斐波那契数,使用生成器)
a, b = 0, 1
for _ in range(n):
yield a
a, b = b, a + b
能够看到提升非常显著:
测试结果汇总
基线:0.083 ns 每个循环
改进:0.004 ns 每个循环
% 改进:95.5 %
加速:22.06x
8、map()函数
使用 Python 内置的 map()函数。它允许在不使用显式 for 循环的情况下处理和转换可迭代对象中的所有项。
def some_function_X(x):
通常这会是一个包含应用逻辑的函数
(为了本次测试的目的,仅计算并返回平方)
return x**2
def test_09_v0(numbers):
基线版本(低效方式)
output = []
for i in numbers:
output.append(some_function_X(i))
return output
def test_09_v1(numbers):
改进版本
(使用 Python 内置的 map()函数)
output = map(some_function_X, numbers)
return output
使用 Python 内置的 map()函数替代显式的 for 循环,实现了 970 倍的加速。
测试结果汇总
基线:4.402 ns 每个循环
改进:0.005 ns 每个循环
% 改进:99.9 %
加速:970.69x
这是为什么呢?
map()函数是用 C 语言编写的,并且经过了高度优化,因此它内部隐含的循环比常规的 Python for 循环高效得多。所以速度得到了提升,或者也可以说 Python 本身还是太慢,哈。
9、使用 Memoization
记忆优化算法的理念是缓存(或“记忆”)昂贵函数调用的结果,并在出现相同输入时返回它们。它能够减少冗余计算,加快程序速度。
首先是低效的版本。
低效代码示例
def fibonacci(n):
if n == 0:
return 0
elif n == 1:
return 1
return fibonacci(n - 1) + fibonacci(n-2)
def test_10_v0(list_of_numbers):
output = []
for i in numbers:
output.append(fibonacci(i))
return output
然后我们使用 Python 内置的 functools 的 lru_cache 函数。
高效代码示例
使用 Python 的 functools 的 lru_cache 函数
import functools
@functools.lru_cache()
def fibonacci_v2(n):
if n == 0:
return 0
elif n == 1:
return 1
return fibonacci_v2(n - 1) + fibonacci_v2(n-2)
def _test_10_v1(numbers):
output = []
for i in numbers:
output.append(fibonacci_v2(i))
return output
结果如下:
测试结果汇总
基线:63.664 ns 每个循环
改进:1.104 ns 每个循环
% 改进:98.3 %
加速:57.69x
使用 Python 的内置 functools 的 lru_cache 函数并运用 Memoization 实现了 57 倍的加速。
lru_cache 函数是如何实现的?
“LRU”是“Least Recently Used”的缩写。lru_cache 是一个装饰器,能够应用于函数以启用 memoization。它会将最近函数调用的结果存储在缓存中,当再次出现相同的输入时,能够提供缓存的结果,从而节省计算时间。lru_cache 函数,在作为装饰器应用时,允许一个可选的 maxsize 参数,maxsize 参数决定了缓存的最大大小(即,它为多少个不同的输入值存储结果)。如果 maxsize 参数设置为 None,则禁用 LRU 特性,缓存可以不受限制地增长,这会消耗大量的内存。这是最简单的空间换时间的优化方法。
10、向量化
import numpy as np
def test_11_v0(n):
基线版本
(低效的求和方式)
output = 0
for i in range(0, n):
output = output + i
return output
def test_11_v1(n):
改进版本
(高效的求和方式)
output = np.sum(np.arange(n))
return output
向量化通常用于机器学习的数据处理库 numpy 和 pandas
测试结果汇总
基线:32.936 ns 每个循环
改进:1.171 ns 每个循环
% 改进:96.4 %
加速:28.13x
11、避免创建中间列表
使用 filterfalse 能够避免创建中间列表。它有助于减少内存使用。
def test_12_v0(numbers):
基线版本(低效方式)
filtered_data = []
for i in numbers:
filtered_data.extend(list(
filter(lambda x: x % 5 == 0,
range(1, i**2))))
return filtered_data
使用 Python 内置的 itertools 的 filterfalse 函数实现相同功能的改进版本。
from itertools import filterfalse
def test_12_v1(numbers):
改进版本
(使用 filterfalse)
filtered_data = []
for i in numbers:
filtered_data.extend(list(
filterfalse(lambda x: x % 5!= 0,
range(1, i**2))))
return filtered_data
这个方法依据具体的用例,执行速度可能没有显著提升,但通过避免创建中间列表能够降低内存使用。我们在此获得了 131 倍的提升
测试结果汇总
基线:333167.790 ns 每个循环
改进:2541.850 ns 每个循环
% 改进:99.2 %
加速:131.07x
12、高效连接字符串
任何使用 + 操作符进行的字符串连接操作都会很慢,并且会消耗更多内存。使用 join 替代。
def test_13_v0(l_strings):
基线版本(低效方式)
(使用 += 操作符进行连接)
output = ""
for a_str in l_strings:
output += a_str
return output
def test_13_v1(numbers):
改进版本
(使用 join)
output_list = []
for a_str in l_strings:
output_list.append(a_str)
return "".join(output_list)
该测试需要一种简便的方法来生成一个较大的字符串列表,所以编写了一个简单的辅助函数来生成运行测试所需的字符串列表。
from faker import Faker
def generate_fake_names(count : int=10000):
辅助函数,用于生成
一个较大的名字列表
fake = Faker()
output_list = []
for _ in range(count):
output_list.append(fake.name())...
相关推荐
- 面试官:来,讲一下枚举类型在开发时中实际应用场景!
-
一.基本介绍枚举是JDK1.5新增的数据类型,使用枚举我们可以很好的描述一些特定的业务场景,比如一年中的春、夏、秋、冬,还有每周的周一到周天,还有各种颜色,以及可以用它来描述一些状态信息,比如错...
- 一日一技:11个基本Python技巧和窍门
-
1.两个数字的交换.x,y=10,20print(x,y)x,y=y,xprint(x,y)输出:102020102.Python字符串取反a="Ge...
- Python Enum 技巧,让代码更简洁、更安全、更易维护
-
如果你是一名Python开发人员,你很可能使用过enum.Enum来创建可读性和可维护性代码。今天发现一个强大的技巧,可以让Enum的境界更进一层,这个技巧不仅能提高可读性,还能以最小的代价增...
- Python元组编程指导教程(python元组的概念)
-
1.元组基础概念1.1什么是元组元组(Tuple)是Python中一种不可变的序列类型,用于存储多个有序的元素。元组与列表(list)类似,但元组一旦创建就不能修改(不可变),这使得元组在某些场景...
- 你可能不知道的实用 Python 功能(python有哪些用)
-
1.超越文件处理的内容管理器大多数开发人员都熟悉使用with语句进行文件操作:withopen('file.txt','r')asfile:co...
- Python 2至3.13新特性总结(python 3.10新特性)
-
以下是Python2到Python3.13的主要新特性总结,按版本分类整理:Python2到Python3的重大变化Python3是一个不向后兼容的版本,主要改进包括:pri...
- Python中for循环访问索引值的方法
-
技术背景在Python编程中,我们经常需要在循环中访问元素的索引值。例如,在处理列表、元组等可迭代对象时,除了要获取元素本身,还需要知道元素的位置。Python提供了多种方式来实现这一需求,下面将详细...
- Python enumerate核心应用解析:索引遍历的高效实践方案
-
喜欢的条友记得关注、点赞、转发、收藏,你们的支持就是我最大的动力源泉。根据GitHub代码分析统计,使用enumerate替代range(len())写法可减少38%的索引错误概率。本文通过12个生产...
- Python入门到脱坑经典案例—列表去重
-
列表去重是Python编程中常见的操作,下面我将介绍多种实现列表去重的方法,从基础到进阶,帮助初学者全面掌握这一技能。方法一:使用集合(set)去重(最简单)pythondefremove_dupl...
- Python枚举类工程实践:常量管理的标准化解决方案
-
本文通过7个生产案例,系统解析枚举类在工程实践中的应用,覆盖状态管理、配置选项、错误代码等场景,适用于Web服务开发、自动化测试及系统集成领域。一、基础概念与语法演进1.1传统常量与枚举类对比#传...
- 让Python枚举更强大!教你玩转Enum扩展
-
为什么你需要关注Enum?在日常开发中,你是否经常遇到这样的代码?ifstatus==1:print("开始处理")elifstatus==2:pri...
- Python枚举(Enum)技巧,你值得了解
-
枚举(Enum)提供了更清晰、结构化的方式来定义常量。通过为枚举添加行为、自动分配值和存储额外数据,可以提升代码的可读性、可维护性,并与数据库结合使用时,使用字符串代替数字能简化调试和查询。Pytho...
- 78行Python代码帮你复现微信撤回消息!
-
来源:悟空智能科技本文约700字,建议阅读5分钟。本文基于python的微信开源库itchat,教你如何收集私聊撤回的信息。[导读]Python曾经对我说:"时日不多,赶紧用Python"。于是看...
- 登录人人都是产品经理即可获得以下权益
-
文章介绍如何利用Cursor自动开发Playwright网页自动化脚本,实现从选题、写文、生图的全流程自动化,并将其打包成API供工作流调用,提高工作效率。虽然我前面文章介绍了很多AI工作流,但它们...
- Python常用小知识-第二弹(python常用方法总结)
-
一、Python中使用JsonPath提取字典中的值JsonPath是解析Json字符串用的,如果有一个多层嵌套的复杂字典,想要根据key和下标来批量提取value,这是比较困难的,使用jsonpat...
你 发表评论:
欢迎- 一周热门
- 最近发表
- 标签列表
-
- python计时 (73)
- python安装路径 (56)
- python类型转换 (93)
- python自定义函数 (53)
- python进度条 (67)
- python吧 (67)
- python字典遍历 (54)
- python的for循环 (65)
- python格式化字符串 (61)
- python串口编程 (60)
- python读取文件夹下所有文件 (59)
- java调用python脚本 (56)
- python操作mysql数据库 (66)
- python字典增加键值对 (53)
- python获取列表的长度 (64)
- python接口 (63)
- python调用函数 (57)
- python人脸识别 (54)
- python多态 (60)
- python命令行参数 (53)
- python匿名函数 (59)
- python打印九九乘法表 (65)
- python赋值 (62)
- python异常 (69)
- python元祖 (57)