加速Python循环的12种方法,最高可以提速900倍

在本文中,我将介绍一些简单的方法,可以将Python for循环的速度提高1.3到900倍。

Python内建的一个常用功能是timeit模块。下面几节中我们将使用它来度量循环的当前性能和改进后的性能。

对于每种方法,我们通过运行测试来建立基线,该测试包括在10次测试运行中运行被测函数100K次(循环),然后计算每个循环的平均时间(以纳秒为单位,ns)。

几个简单方法

1、列表推导式

 # Baseline version (Inefficient way)# Calculating the power of numbers# Without using List Comprehensiondeftest_01_v0(numbers):output= []forninnumbers:output.append(n**2.5)returnoutput# Improved version# (Using List Comprehension)deftest_01_v1(numbers):output= [n**2.5forninnumbers]returnoutput

结果如下:

 # Summary Of Test ResultsBaseline: 32.158 ns per loopImproved: 16.040 ns per loop% Improvement: 50.1 %Speedup: 2.00x

可以看到使用列表推导式可以得到2倍速的提高

2、在外部计算长度

如果需要依靠列表的长度进行迭代,请在for循环之外进行计算。

 # Baseline version (Inefficient way)# (Length calculation inside for loop)deftest_02_v0(numbers):output_list= []foriinrange(len(numbers)):output_list.append(i*2)returnoutput_list# Improved version# (Length calculation outside for loop)deftest_02_v1(numbers):my_list_length=len(numbers)output_list= []foriinrange(my_list_length):output_list.append(i*2)returnoutput_list

通过将列表长度计算移出for循环,加速1.6倍,这个方法可能很少有人知道吧。

 # Summary Of Test ResultsBaseline: 112.135 ns per loopImproved:  68.304 ns per loop% Improvement: 39.1 %Speedup: 1.64x

3、使用Set

在使用for循环进行比较的情况下使用set。

 # Use for loops for nested lookupsdeftest_03_v0(list_1, list_2):# Baseline version (Inefficient way)# (nested lookups using for loop)common_items= []foriteminlist_1:ifiteminlist_2:common_items.append(item)returncommon_itemsdeftest_03_v1(list_1, list_2):# Improved version# (sets to replace nested lookups)s_1=set(list_1)s_2=set(list_2)output_list= []common_items=s_1.intersection(s_2)returncommon_items

在使用嵌套for循环进行比较的情况下,使用set加速498x

 # Summary Of Test ResultsBaseline: 9047.078 ns per loopImproved:   18.161 ns per loop% Improvement: 99.8 %Speedup: 498.17x

4、跳过不相关的迭代

避免冗余计算,即跳过不相关的迭代。

 # Example of inefficient code used to find # the first even square in a list of numbersdeffunction_do_something(numbers):forninnumbers:square=n*nifsquare%2==0:returnsquarereturnNone  # No even square found# Example of improved code that # finds result without redundant computationsdeffunction_do_something_v1(numbers):even_numbers= [iforninnumbersifn%2==0]fornineven_numbers:square=n*nreturnsquarereturnNone  # No even square found

这个方法要在设计for循环内容的时候进行代码设计,具体能提升多少可能根据实际情况不同:

 # Summary Of Test ResultsBaseline: 16.912 ns per loopImproved:  8.697 ns per loop% Improvement: 48.6 %Speedup: 1.94x

5、代码合并

在某些情况下,直接将简单函数的代码合并到循环中可以提高代码的紧凑性和执行速度。

 # Example of inefficient code# Loop that calls the is_prime function n times.defis_prime(n):ifn<=1:returnFalseforiinrange(2, int(n**0.5) +1):ifn%i==0:returnFalsereturnTruedeftest_05_v0(n):# Baseline version (Inefficient way)# (calls the is_prime function n times)count=0foriinrange(2, n+1):ifis_prime(i):count+=1returncountdeftest_05_v1(n):# Improved version# (inlines the logic of the is_prime function)count=0foriinrange(2, n+1):ifi<=1:continueforjinrange(2, int(i**0.5) +1):ifi%j==0:breakelse:count+=1returncount

这样也可以提高1.3倍

 # Summary Of Test ResultsBaseline: 1271.188 ns per loopImproved:  939.603 ns per loop% Improvement: 26.1 %Speedup: 1.35x

这是为什么呢?

调用函数涉及开销,例如在堆栈上推入和弹出变量、函数查找和参数传递。当一个简单的函数在循环中被重复调用时,函数调用的开销会增加并影响性能。所以将函数的代码直接内联到循环中可以消除这种开销,从而可能显著提高速度。

⚠️但是这里需要注意,平衡代码可读性和函数调用的频率是一个要考虑的问题。

一些小技巧

6 .避免重复

考虑避免重复计算,其中一些计算可能是多余的,并且会减慢代码的速度。相反,在适用的情况下考虑预计算。

 deftest_07_v0(n):# Example of inefficient code# Repetitive calculation within nested loopresult=0foriinrange(n):forjinrange(n):result+=i*jreturnresultdeftest_07_v1(n):# Example of improved code# Utilize precomputed values to help speeduppv= [[i*jforjinrange(n)] foriinrange(n)]result=0foriinrange(n):result+=sum(pv[i][:i+1])returnresult

结果如下

 # Summary Of Test ResultsBaseline: 139.146 ns per loopImproved:  92.325 ns per loop% Improvement: 33.6 %Speedup: 1.51x

7、使用Generators

生成器支持延迟求值,也就是说,只有当你向它请求下一个值时,里面的表达式才会被求值,动态处理数据有助于减少内存使用并提高性能。尤其是大型数据集中

 deftest_08_v0(n):# Baseline version (Inefficient way)# (Inefficiently calculates the nth Fibonacci# number using a list)ifn<=1:returnnf_list= [0, 1]foriinrange(2, n+1):f_list.append(f_list[i-1] +f_list[i-2])returnf_list[n]deftest_08_v1(n):# Improved version# (Efficiently calculates the nth Fibonacci# number using a generator)a, b=0, 1for_inrange(n):yieldaa, b=b, a+b

可以看到提升很明显:

 # Summary Of Test ResultsBaseline: 0.083 ns per loopImproved: 0.004 ns per loop% Improvement: 95.5 %Speedup: 22.06x

8、map()函数

使用Python内置的map()函数。它允许在不使用显式for循环的情况下处理和转换可迭代对象中的所有项。

 defsome_function_X(x):# This would normally be a function containing application logic# which required it to be made into a separate function# (for the purpose of this test, just calculate and return the square)returnx**2deftest_09_v0(numbers):# Baseline version (Inefficient way)output= []foriinnumbers:output.append(some_function_X(i))returnoutputdeftest_09_v1(numbers):# Improved version# (Using Python's built-in map() function)output=map(some_function_X, numbers)returnoutput

使用Python内置的map()函数代替显式的for循环加速了970x。

 # Summary Of Test ResultsBaseline: 4.402 ns per loopImproved: 0.005 ns per loop% Improvement: 99.9 %Speedup: 970.69x

这是为什么呢?

map()函数是用C语言编写的,并且经过了高度优化,因此它的内部隐含循环比常规的Python for循环要高效得多。因此速度加快了,或者可以说Python还是太慢,哈。

9、使用Memoization

记忆优化算法的思想是缓存(或“记忆”)昂贵的函数调用的结果,并在出现相同的输入时返回它们。它可以减少冗余计算,加快程序速度。

首先是低效的版本。

 # Example of inefficient codedeffibonacci(n):ifn==0:return0elifn==1:return1returnfibonacci(n-1) +fibonacci(n-2)deftest_10_v0(list_of_numbers):output= []foriinnumbers:output.append(fibonacci(i))returnoutput

然后我们使用Python的内置functools的lru_cache函数。

 # Example of efficient code# Using Python's functools' lru_cache functionimportfunctools@functools.lru_cache()deffibonacci_v2(n):ifn==0:return0elifn==1:return1returnfibonacci_v2(n-1) +fibonacci_v2(n-2)def_test_10_v1(numbers):output= []foriinnumbers:output.append(fibonacci_v2(i))returnoutput

结果如下:

 # Summary Of Test ResultsBaseline: 63.664 ns per loopImproved:  1.104 ns per loop% Improvement: 98.3 %Speedup: 57.69x

使用Python的内置functools的lru_cache函数使用Memoization加速57x。

lru_cache函数是如何实现的?

“LRU”是“Least Recently Used”的缩写。lru_cache是一个装饰器,可以应用于函数以启用memoization。它将最近函数调用的结果存储在缓存中,当再次出现相同的输入时,可以提供缓存的结果,从而节省了计算时间。lru_cache函数,当作为装饰器应用时,允许一个可选的maxsize参数,maxsize参数决定了缓存的最大大小(即,它为多少个不同的输入值存储结果)。如果maxsize参数设置为None,则禁用LRU特性,缓存可以不受约束地增长,这会消耗很多的内存。这是最简单的空间换时间的优化方法。

10、向量化

 importnumpyasnpdeftest_11_v0(n):# Baseline version# (Inefficient way of summing numbers in a range)output=0foriinrange(0, n):output=output+ireturnoutputdeftest_11_v1(n):# Improved version# (# Efficient way of summing numbers in a range)output=np.sum(np.arange(n))returnoutput

向量化一般用于机器学习的数据处理库numpy和pandas

 # Summary Of Test ResultsBaseline: 32.936 ns per loopImproved:  1.171 ns per loop% Improvement: 96.4 %Speedup: 28.13x

11、避免创建中间列表

使用filterfalse可以避免创建中间列表。它有助于使用更少的内存。

 deftest_12_v0(numbers):# Baseline version (Inefficient way)filtered_data= []foriinnumbers:filtered_data.extend(list(filter(lambdax: x%5==0,range(1, i**2))))returnfiltered_data

使用Python的内置itertools的filterfalse函数实现相同功能的改进版本。

 fromitertoolsimportfilterfalsedeftest_12_v1(numbers):# Improved version# (using filterfalse)filtered_data= []foriinnumbers:filtered_data.extend(list(filterfalse(lambdax: x%5!=0,range(1, i**2))))returnfiltered_data

这个方法根据用例的不同,执行速度可能没有显著提高,但通过避免创建中间列表可以降低内存使用。我们这里获得了131倍的提高

 # Summary Of Test ResultsBaseline: 333167.790 ns per loopImproved: 2541.850 ns per loop% Improvement: 99.2 %Speedup: 131.07x

12、高效连接字符串

任何使用+操作符的字符串连接操作都会很慢,并且会消耗更多内存。使用join代替。

 deftest_13_v0(l_strings):# Baseline version (Inefficient way)# (concatenation using the += operator)output=""fora_strinl_strings:output+=a_strreturnoutputdeftest_13_v1(numbers):# Improved version# (using join)output_list= []fora_strinl_strings:output_list.append(a_str)return"".join(output_list)

该测试需要一种简单的方法来生成一个较大的字符串列表,所以写了一个简单的辅助函数来生成运行测试所需的字符串列表。

 fromfakerimportFakerdefgenerate_fake_names(count : int=10000):# Helper function used to generate a # large-ish list of namesfake=Faker()output_list= []for_inrange(count):output_list.append(fake.name())returnoutput_listl_strings=generate_fake_names(count=50000)

结果如下:

 # Summary Of Test ResultsBaseline: 32.423 ns per loopImproved: 21.051 ns per loop% Improvement: 35.1 %Speedup: 1.54x

使用连接函数而不是使用+运算符加速1.5倍。为什么连接函数更快?

使用+操作符的字符串连接操作的时间复杂度为O(n²),而使用join函数的字符串连接操作的时间复杂度为O(n)。

总结

本文介绍了一些简单的方法,将Python for循环的提升了1.3到970x。

  • 使用Python内置的map()函数代替显式的for循环加速970x
  • 使用set代替嵌套的for循环加速498x[技巧#3]
  • 使用itertools的filterfalse函数加速131x
  • 使用lru_cache函数使用Memoization加速57x

https://avoid.overfit.cn/post/b01a152cfb824acc86f5118431201fe3

作者:Nirmalya Ghosh

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/315060.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

上海三菱电梯搭建销售节点管理平台,可视化管控销售全流程

上海三菱电梯有限公司 上海三菱电梯有限公司成立于1987年1月&#xff0c;由上海机电股份有限公司和日本三菱电机株式会社等四方合资组成&#xff0c;是由中方控股和管理的中日合资大型电梯企业&#xff0c;是中国机械制造业和外商投资企业500强企业&#xff0c;也是中国机械工…

如何基于 ESP32-S3 和 ESP32-H2 产品构建 WiFi +Thread 边界路由器?

参考文档&#xff1a; Building the images for ESP Thread Border Router and CLI device and forming a Thread network with the devices 硬件准备&#xff1a; ESP32-S3-DevKitC-1ESP32-H2-DevKitM-1 软件准备&#xff1a; esp-idf/ examples/ openthread/ ot_rcp&#x…

红队攻防实战之DC2

吾愿效法古圣先贤&#xff0c;使成千上万的巧儿都能在21世纪的中华盛世里&#xff0c;丰衣足食&#xff0c;怡然自得 0x01 信息收集: 1.1 端口探测 使用nmap工具 可以发现开放了80端口&#xff0c;网页服务器但是可以看出做了域名解析&#xff0c;所以需要在本地完成本地域名…

三、HTML元素

一、HTML元素 HTML 文档由 HTML 元素定义。 *开始标签常被称为起始标签&#xff08;opening tag&#xff09;&#xff0c;结束标签常称为闭合标签&#xff08;closing tag&#xff09;。 二、HTML 元素语法 HTML 元素以开始标签起始。HTML 元素以结束标签终止。元素的内容是…

2024年AI领域的突破性进展预测

&#x1f989; AI新闻 &#x1f680; 2024年AI领域的突破性进展预测 摘要&#xff1a;23年被誉为生成式AI之年&#xff0c;24年AI有哪些新突破&#xff1f;GPT-5发布后&#xff0c;LLM在本质上仍然有限&#xff0c;基本的AGI也不足以实现。然而&#xff0c;英伟达高级科学家和…

基于Spring Boot的美妆分享系统:打造个性化推荐、互动社区与智能决策

基于Spring Boot的美妆分享系统&#xff1a;打造个性化推荐、互动社区与智能决策 1. 项目介绍2. 管理员功能2.1 美妆管理2.2 页面管理2.3 链接管理2.4 评论管理2.5 用户管理2.6 公告管理 3. 用户功能3.1 登录注册3.2 分享商品3.3 问答3.4 我的分享3.5 我的收藏夹 4. 创新点4.1 …

C++-友元-string字符串类

1、友元 1.1 概念 类实现了数据的隐藏和封装&#xff0c;类的数据一般定义为私有成员&#xff0c;仅能通过类的成员函数才能读写。如果数据成员定义为公有的&#xff0c;则破坏了类的封装性。但是某些情况下&#xff0c;需要频繁的读写类的成员函数&#xff0c;特别是在对成员函…

Pytorch详细安装过程

1、安装anaconda 官网&#xff08;https://www.anaconda.com/products/distribution#Downloads&#xff09;下载&#xff0c;使用管理员身份运行&#xff08;不使用似乎也没事&#xff09; 这里选择Just me&#xff08;至于为啥&#xff0c;咱也不是很清楚&#xff09; 更改路…

RocketMQ 生产者源码分析:DefaultMQProducer、DefaultMQProducerImpl

&#x1f52d; 嗨&#xff0c;您好 &#x1f44b; 我是 vnjohn&#xff0c;在互联网企业担任 Java 开发&#xff0c;CSDN 优质创作者 &#x1f4d6; 推荐专栏&#xff1a;Spring、MySQL、Nacos、Java&#xff0c;后续其他专栏会持续优化更新迭代 &#x1f332;文章所在专栏&…

idea中java maven程序打JAR包的方式

JAR包是一种文件格式&#xff0c;用于将Java类、资源和元数据打包到一个文件中。它通常用于将Java库、应用程序或模块分发给其他开发人员或部署到不同的环境中。JAR包可以包含许多不同类型的文件&#xff0c;包括.class文件&#xff08;编译后的Java类&#xff09;、.java文件&…

ubuntu20.4 静态网络配置(保姆级图文教程)

之前一直使用的Linux系统都是centOs&#xff0c;突然换成Ubuntu之后不知道怎么配置网络&#xff0c;网上查找了很多资料都不可用&#xff0c;最后终于在一篇博客里看到了20.4版本的网络配置教程&#xff0c;在此贴上链接&#xff0c;并记录 Linux ubuntu20.04 网络配置&#x…

【C语言】隐式类型转换和强制类型转换详解

文章目录 前言隐式类型转换整型提升算数转换 强制类型转换总结 前言 提示&#xff1a;这里可以添加本文要记录的大概内容&#xff1a; C语言中的数据类型是编程中一个至关重要的概念。在处理不同数据类型的操作时&#xff0c;我们经常需要考虑类型之间的转换。C语言提供了两种…