本文主要分享几种提高Python性能的使用方法!在进行时序分析和优化之前,我们首先要找出是哪部分代码拖慢了整个程序的运行。有时程序的“瓶颈”并不明显。如果找不到,这里有一些建议供参考:注意:这是一个演示程序(来自Python文档),计算e的x次方:#slow_program.pyfromdecimalimport*defexp(x):getcontext().prec+=2i,lasts,s,fact,num=0,0,1,1,1whiles!=lasts:lasts=si+=1fact*=inum*=xs+=num/factgetcontext().prec-=2return+sexp(Decimal(150))exp(Decimal(400))exp(Decimal(3000))在GitHub上查看rawslow_program.py的所有代码最省力的《性能分析》一、最简单最省力的解决方案是使用Unix的时间命令:~$timepython3.8slow_program.pyreal0m11,058suser0m11,050ssys0m0,008s查看GitHub上的rawbase_time.shell完整代码如果你只是对整个程序计时,它很有用,但还不够......最详细的性能分析另一个性能分析的方法是cProfile,从中可以获取大量信息:~$python3.8-mcProfile-stimeslow_program.py1297functioncalls(1272primitivecalls)in11.081secondsOrderedby:internaltimencallstottimepercallcumtimepercallfilename:lineno(函数)311.0793.079311。693slow_program.py:4(exp)10.0000.0000.0020.002{built-inmethod_imp.create_dynamic}4/10.0000.00011.08111.081{built-inmethodbuiltins.exec}60.0000.0000.0000.000{built-inmethod__new__oftypeobjectat0x9d120}.0000.0000.000abc.py:132(__new__)230.0000.0000.0000.000_weakrefset.py:36(__init__)2450.0000.0000.0000.000{built-inmethodbuiltins.getattr}20.0000.0000.0000.000{built-inmethodmarshal.loads}100.0000.0000.0000.000:1233(find_spec)8/40.0000.0000.0000.000abc.py:196(__subclasscheck__)150.0000.0000.0000.000{内置方法posix.stat}60.0000.0000.0000.000{内置方法}内置方法__内置类10.0000.0000.0000.000__init__.py:357(MandeDtuple)480.0000.0000.0000.0000.000:57(_PATH_JOIN_JOIN_jOINpy:1()...在GitHub上查看rawcprofile.shell的完整代码在这里,您使用cProfile模块和时间参数运行测试脚本,以按内部时间(cumtime)对行进行排序,您可以从中得到了很多信息,上面所有的列结果都是实际输出的10%左右。可以看出exp函数是拖慢程序的“罪魁祸首”(太神奇了!),现在看更详细的计时和性能分析……计时特定函数已经知道会拖慢程序,下一步是使用一个简单的装饰器专门为这个函数计时,而不是其余代码。如下:deftimeit_wrapper(func):@wraps(func)defwrapper(*args,**kwargs):start=time.perf_counter()#Alternatively,youcanusetime.process_time()funcfunc_return_val=func(*args,**kwargs)end=time.perf_counter()print({0:<10}.{1:<8}:{2:<8}.format(func.__module__,func.__name__,end-start))returnfunc_return_valreturnwrapper在GitHub上查看整个rawtimeit_decorator.py的代码装饰器可以应用于功能测试,如下所示:@timeit_wrapperdefexp(x):...print({0:<10}{1:<8}{2:^8}.format(module,function,time))exp(Decimal(150))exp(Decimal(400))exp(Decimal(3000))在GitHub上查看rawtimeit_decorator_usage.py的输出如下:~$python3.8slow_program.pymodulefunctiontime__main__.exp:0.003267502994276583__main__.exp:0.038535295985639095__main__.exp:11.728486061969306在GitHub上查看rawrun_with_timeit_decorator.shell的完整代码需要考虑的一个问题是您实际/想要测量的时间类型。Time包提供了time.perf_counter和time.process_time。两者的区别在于:perf_counter返回的是绝对值,其中包含了Python程序进程未运行的时间,因此可能会受到计算机负载的影响;而process_time只返回用户时间(不包括系统时间),也只是程序运行时间。加快程序运行速度来源:Unsplash这是全文比较有意思的部分,讲的是如何加快Python程序的运行速度。我没有列出一些可以神奇地解决性能问题的小技巧或代码片段,而是列出可以显着提高性能的一般想法和策略,在某些情况下可以提高30%。使用内置数据类型是显而易见的,而且内置数据类型速度很快,尤其是与树或链表等自定义类型相比。主要是因为内置程序是用C语言实现的,远远超过用Python编码的运行速度。使用lru_cache缓存/记忆我已经在之前的博文中谈到了这一点,但这里有一个简单的例子:importfunctoolsimporttime#cachingupto12differentresults@functools.lru_cache(maxsize=12)defslow_func(x):time.sleep(2)#Simulatelongcomputationreturnxslow_func(1)#...waitingfor2secbeforegettingresultslow_func(1)#alreadycached-resultreturnedinstantaneously!slow_func(3)#...waitingfor2secbeforegettingresult查看github上rawlru_cache.py的所有代码以上函数使用time.sleep模拟大量操作。第一次带参数1调用函数,需要2秒返回结果。再次调用时,结果已经缓存,因此跳过函数体,立即返回结果。在这里阅读更多。使用局部变量这与在每个范围内查找变量的速度有关。我使用术语“每个作用域”,因为这不仅仅是使用局部变量还是全局变量的问题。事实上,查找速度甚至在函数的局部变量(最快)、类级属性(如self.name-较慢)和全局变量(如导入函数,time.time-最慢)之间也不同。可以通过运行无用的任务来提高性能,如下所示:#Example#1classFastClass:defdo_stuff(self):temp=self.value#thisspeedsuplookupinloopforiinrange(10000):...#Dosomethingwith`temp`here#Example#2importrandomdefast_function():r=random.randomforiinrange(10000):print(r())#calling`r()`这里,isfasterthanglobalrandom.random()在GitHub上查看rawlocal_vars.py的所有代码这和想象中的有什么区别?理论上调用一个函数不会把更多的东西放到栈上,增加返回结果的负担吧?但实际上,使用函数确实可以加快速度,这与前一点有关。将整个代码放在一个文件而不是一个函数中,它是一个全局变量而不是局部变量,它运行起来会慢得多。因此,可以将整个代码包裹在main函数中,并进行一次调用来加速代码,如下:defmain():...#Allyourpreviouslyglobalcodemain()所有代码见GitHub上的rawglobal_vars.py避免访问属性(Attribute)可能导致程序变慢的一个原因是使用点运算符(.)访问对象属性。此运算符使用__getattribute__方法触发字典查找,从而增加了代码的额外开销。那么,如何避免或减少属性访问呢?#Slow:importredefslow_func():foriinrange(10000):re.findall(regex,line)#Slow!#Fast:fromreimportfindalldeffast_func():foriinrange(10000):findall(regex,line)#更快!查看GitHub上的原始导入。py整个代码小心使用字符串在循环中使用格式说明符(%s)或.format()时,字符串操作会变得非常慢。有更好的选择吗?RaymondHettinger在最近的一条推文中提到:你唯一应该使用的是f-string(格式化字符串常量),它是最易读、简洁和最快的方式。根据这条推文,下面列出了可用的方法(从快到慢):f{s}{t}#Fast!s++t.join((s,t))%s%s%(s,t){}{}.format(s,t)Template($s$t).substitute(ss=s,tt=t)#慢!查看GitHub上的rawstrings.py以获取完整代码从本质上讲,生成器并没有变得更快,因为它旨在允许延迟计算以节省内存而不是时间。但是,节省的内存也可以加快程序的实际执行速度。怎么做?如果数据集很大,没有使用生成器(迭代器),数据可能会溢出CPU的L1缓存(一级缓存),这会大大减慢内存查找速度。就性能而言,CPU将其正在处理的所有数据尽可能多地保存在缓存中是极其重要的。来源:Unsplash尾声优化的第一条规则是“不要优化”。如果真的需要优化,那么我希望这些技巧能有所帮助。但是,在优化代码时一定要小心,因为优化的结果可能是代码难以阅读,从而难以维护,得不偿失。最后,希望大家能赶上python的火箭,代码越来越快!