加速 Python for 循环(python加快循环速度)
off999 2024-10-23 12:50 27 浏览 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())...
相关推荐
- 大文件传不动?WinRAR/7-Zip 入门到高手,这 5 个技巧让你效率翻倍
-
“这200张照片怎么传给女儿?微信发不了,邮箱附件又超限……”62岁的张阿姨对着电脑犯愁时,儿子只用了3分钟就把照片压缩成一个文件,还教她:“以后用压缩软件,比打包行李还方便!”职场人更懂这...
- 电脑解压缩软件推荐——7-Zip:免费、高效、简洁的文件管理神器
-
在日常工作中,我们经常需要处理压缩文件。无论是下载软件包、接收文件,还是存储大量数据,压缩和解压缩文件都成为了我们日常操作的一部分。而说到压缩解压软件,7-Zip绝对是一个不可忽视的名字。今天,我就来...
- 设置了加密密码zip文件要如何打开?这几个方法可以试试~
-
Zip是一种常见的压缩格式文件,文件还可以设置密码保护。那设置了密码的Zip文件要如何打开呢?不清楚的小伙伴一起来看看吧。当我们知道密码想要打开带密码的Zip文件,我们需要用到适用于Zip格式的解压缩...
- 大文件想要传输成功,怎么把ZIP文件分卷压缩
-
不知道各位小伙伴有没有这样的烦恼,发送很大很大的压缩包会受到限制,为此,想要在压缩过程中将文件拆分为几个压缩包并且同时为所有压缩包设置加密应该如何设置?方法一:使用7-Zip免费且强大的文件管理工具7...
- 高效处理 RAR 分卷压缩包:合并解压操作全攻略
-
在文件传输和存储过程中,当遇到大文件时,我们常常会使用分卷压缩的方式将其拆分成多个较小的压缩包,方便存储和传输。RAR作为一种常见的压缩格式,分卷压缩包的使用频率也很高。但很多人在拿到RAR分卷...
- 2个方法教你如何删除ZIP压缩包密码
-
zip压缩包设置了加密密码,每次解压文件都需要输入密码才能够顺利解压出文件,当压缩包文件不再需要加密的时候,大家肯定想删除压缩包密码,或是忘记了压缩包密码,想要通过删除操作将压缩包密码删除,就能够顺利...
- 速转!漏洞预警丨压缩软件Winrar目录穿越漏洞
-
WinRAR是一款功能强大的压缩包管理器,它是档案工具RAR在Windows环境下的图形界面。该软件可用于备份数据,缩减电子邮件附件的大小,解压缩从Internet上下载的RAR、ZIP及其它类...
- 文件解压方法和工具分享_文件解压工具下载
-
压缩文件减少文件大小,降低文件失效的概率,总得来说好处很多。所以很多文件我们下载下来都是压缩软件,很多小伙伴不知道怎么解压,或者不知道什么工具更好,所以今天做了文件解压方法和工具的分享给大家。一、解压...
- [python]《Python编程快速上手:让繁琐工作自动化》学习笔记3
-
1.组织文件笔记(第9章)(代码下载)1.1文件与文件路径通过importshutil调用shutil模块操作目录,shutil模块能够在Python程序中实现文件复制、移动、改名和删除;同时...
- Python内置tarfile模块:读写 tar 归档文件详解
-
一、学习目标1.1学习目标掌握Python内置模块tarfile的核心功能,包括:理解tar归档文件的原理与常见压缩格式(gzip/bz2/lzma)掌握tar文件的读写操作(创建、解压、查看、过滤...
- 使用python展开tar包_python拓展
-
类Unix的系统,打包文件经常使用的就是tar包,结合zip工具,可以方便的打包并解压。在python的标准库里面有tarfile库,可以方便实现生成了展开tar包。使用这个库最大的好处,可能就在于不...
- 银狐钓鱼再升级:白文件脚本化实现GO语言后门持久驻留
-
近期,火绒威胁情报中心监测到一批相对更为活跃的“银狐”系列变种木马。火绒安全工程师第一时间获取样本并进行分析。分析发现,该样本通过阿里云存储桶下发恶意文件,采用AppDomainManager进行白利...
- ZIP文件怎么打开?2个简单方法教你轻松搞定!
-
在日常工作和生活中,我们经常会遇到各种压缩文件,其中最常见的格式之一就是ZIP。ZIP文件通过压缩数据来减少文件大小,方便我们进行存储和传输。然而,对于初学者来说,如何打开ZIP文件可能会成为一个小小...
- Ubuntu—解压多个zip压缩文件.zip .z01 .z02
-
方法将所有zip文件放在同一目录中:zip_file.z01,zip_file.z02,zip_file.z03,...,zip_file.zip。在Zip3.0版本及以上,使用下列命令:将所有zi...
- 如何使用7-Zip对文件进行加密压缩
-
7-Zip是一款开源的文件归档工具,支持多种压缩格式,并提供了对压缩文件进行加密的功能。使用7-Zip可以轻松创建和解压.7z、.zip等格式的压缩文件,并且可以通过设置密码来保护压缩包中的...
你 发表评论:
欢迎- 一周热门
- 最近发表
- 标签列表
-
- python计时 (73)
- python安装路径 (56)
- python类型转换 (93)
- python进度条 (67)
- python吧 (67)
- python的for循环 (65)
- python格式化字符串 (61)
- python静态方法 (57)
- python列表切片 (59)
- python面向对象编程 (60)
- python 代码加密 (65)
- python串口编程 (77)
- python封装 (57)
- python写入txt (66)
- python读取文件夹下所有文件 (59)
- python操作mysql数据库 (66)
- python获取列表的长度 (64)
- python接口 (63)
- python调用函数 (57)
- python多态 (60)
- python匿名函数 (59)
- python打印九九乘法表 (65)
- python赋值 (62)
- python异常 (69)
- python元祖 (57)