百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 技术资源 > 正文

加速 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...

取消回复欢迎 发表评论: