全文共5573字,预计学习时长16分钟
图源:Unsplash
Python是一个很酷的语言,由于你能够在很短的时间内利用很少的代码做非常多事情。不仅如此,它还能容易地支持多任务,例如多进程等。
但Python运行的慢是历来被诟病的,有些人黑Python的一点是Python的程序运行速度奇慢。
这一方面和语言相关,另一方面可能便是你代码的问题。其实,无论运用哪种编程语言,特定程序的运行速度很大程度上都取决于该程序的研发人员及其编写快而优的程序的技巧和能力。
语言方面的问题咱们处理不了,因此只能在编程技巧上来加强程序的运行效率。
是时候证明给哪些python黑粉,让她们瞧瞧怎样提高Python程序性能并使其像坐上火箭同样运行飞快!
图源:Unsplash
时序分析
优化之前,首要要找到是哪部分代码拖慢了全部程序的运行。有时候程序的"瓶颈"不是很显著,倘若找不到,以下是有些意见以供参考:
重视:这是一个计算e的x次幂的演示程序(出自Python文档): # slow_program.pyfrom decimal import*defexp(x):getcontext().prec +=2i, lasts, s, fact, num =0, 0, 1, 1, 1while s != lasts:lasts = si +=1fact *= inum *= xs += num / factgetcontext().prec -=2return+sexp(Decimal(150))exp(Decimal(400))exp(Decimal(3000))在GitHub上查看rawslow_program.py所有代码
最省力的“性能分析”
首要,最简单且最省力的处理方法是运用Unix的time命令: ~ $ time python3.8 slow_program.pyreal 0m11,058suser 0m11,050ssys 0m0,008s在GitHub上查看rawbase_time.shell所有代码
倘若只是给全部程序计时,它特别有用,但还不足够……
最仔细的性能分析
性能分析的另一办法是cProfile,从中能得到很大的信息量: ~ $ python3.8 -m cProfile -s time slow_program.py1297 function calls (1272 primitive calls) in 11.081 secondsOrdered by: internal timencalls tottime percall cumtime percall filename:lineno(function)3 11.079 3.693 11.079 3.693 slow_program.py:4(exp)1 0.000 0.000 0.002 0.002 {built-in method _imp.create_dynamic}4/1 0.000 0.000 11.081 11.081 {built-in method builtins.exec}6 0.000 0.000 0.000 0.000 {built-in method __new__ of type object at 0x9d12c0}6 0.000 0.000 0.000 0.000 abc.py:132(__new__)23 0.000 0.000 0.000 0.000 _weakrefset.py:36(__init__)245 0.000 0.000 0.000 0.000 {built-in method builtins.getattr}2 0.000 0.000 0.000 0.000 {built-in method marshal.loads}10 0.000 0.000 0.000 0.000 <frozen importlib._bootstrap_external>:1233(find_spec)8/4 0.000 0.000 0.000 0.000 abc.py:196(__subclasscheck__)15 0.000 0.000 0.000 0.000 {built-in method posix.stat}6 0.000 0.000 0.000 0.000 {built-in method builtins.__build_class__}1 0.000 0.000 0.000 0.000 __init__.py:357(namedtuple)48 0.000 0.000 0.000 0.000 <frozen importlib._bootstrap_external>:57(_path_join)48 0.000 0.000 0.000 0.000 <frozen importlib._bootstrap_external>:59(<listcomp>)1 0.000 0.000 11.081 11.081 slow_program.py:1(<module>)...在GitHub上查看rawcprofile.shell所有代码
这儿用cProfile模块和time参数运行测试脚本,以便按内部时间(cumtime)对行进行排序。从中能够得到非常多信息,以上所列结果约为实质输出的10%。由此可见,exp函数便是拖慢程序的“罪魁祸首”(太神奇啦!),现在瞧瞧更详尽的时序和性能分析......
对特定函数计时
已然晓得拖慢程序运行的函数,下一步可运用简单的修饰器,专门对该函数计时,不测绘其余代码。如下所示: deftimeit_wrapper(func)wraps(func)defwrapper(*args, **kwargs):start = time.perf_counter() # Alternatively, you can use time.process_time()func_return_val = func(*args, **kwargs)end = time.perf_counter()print({0:<10}.{1:<8} : {2:<8}.format(func.__module__, func.__name__, end - start))return func_return_valreturn wrapper在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.8 slow_program.pymodule function time__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缓存/记忆
我已然在上一篇博文中讲过这块内容,但这里还是要用简单的示例说明: import functoolsimport time# caching up to 12 different results@functools.lru_cache(maxsize=12)defslow_func(x):time.sleep(2) # Simulate long computationreturn xslow_func(1) # ... waiting for 2 sec before getting resultslow_func(1) # already cached - result returned instantaneously!slow_func(3) # ... waiting for 2 sec before getting result在GitHub上查看rawlru_cache.py所有代码
以上函数运用time.sleep模拟海量运算。第1次运用参数1调用该函数时,返回结果必须2秒。再次调用时,结果已被缓存,因此呢会跳过函数主体并立即返回结果。更加多内容请参见此处。
运用局部变量
这与在每一个功效域中查询变量的速度相关。我用了“每一个功效域”这个字眼,由于它不仅是“运用局部变量还是全局变量”的问题。实质上,即使在函数的局部变量(最快)、类级属性(如self.name-较慢)和全局变量(如导入的函数,time.time-最慢)之间,查询速度亦有所区别。
能够经过运行无用的任务来加强性能,如下所示: # Example #1classFastClass:defdo_stuff(self):temp =self.value # this speeds up lookup in loopfor i inrange(10000):... # Do something with `temp` here# Example #2import randomdeffast_function():r = random.randomfor i inrange(10000):print(r()) # calling `r()` here, is faster than global random.random()在GitHub上查看rawlocal_vars.py所有代码
运用函数(Function)
这怎么和假想的区别?理论上调用函数不是会将更加多的东西放到堆栈上,加大返回结果的包袱吗?但实质上,运用函数确实能加快运行速度,这与前一点相关。将全部代码放在一个文件中而非函数中,它是全局变量而非局部变量,运行速度就会慢得多。因此呢,能够将全部代码包裹在main函数中并经过一次调用来加速代码,如下所示: defmain():... # All your previously global codemain()在GitHub上查看rawglobal_vars.py所有代码
避免拜访属性(Attribute)
可能拖慢程序的一个原由是运用点运算符(.)拜访对象属性。该运算符经过运用__getattribute__办法触发了字典查询,使代码产生额外包袱。那样,怎样避免或减少属性拜访? # Slow:import redefslow_func():for i inrange(10000):re.findall(regex, line) # Slow!# Fast:from re import findalldeffast_func():for i inrange(10000):findall(regex, line) # Faster!在GitHub上查看rawimports.py所有代码
小心运用字符串
在循环里运用格式符(%s)或.format()时,字符串操作可能会变得非常慢。有无更好的选取?Raymond Hettinger在近期发布的推文中说到:唯一应该运用的是f-string(格式化字符串常量),它是最易读、最简洁且最快捷的办法。按照这篇推文,下面列出了可用的办法(由快到慢): f{s}{t} # Fast!s + + t .join((s, t))%s %s% (s, t){} {}.format(s, t)Template($s $t).substitute(s=s, t=t) # Slow!在GitHub上查看rawstrings.py所有代码
本质上,生成器并无变得更快,由于它在设计上准许延迟计算以节省内存而非节约时间。然而节省的内存亦能够加快程序实质运行速度。怎么做?倘若有一个很大的数据集且不运用生成器(迭代器),那样数据可能会溢出CPU的L1 cache(1级缓存),这将大大减慢内存的查询速度。
在性能方面,极重要的一点是:CPU能够将正在处理的所有数据尽可能地保留在缓存中。
图源:Unsplash
结语
优化的首要规则便是“不优化”。
若真的有必要优化,那我期盼这些技巧会有所帮忙。
然则,优化代码时必定要小心,由于优化的结果可能是代码难以阅读从而难以守护,这就得不偿失了。
最后,期盼大众能搭上python号火箭,编码越来越快!
留言点赞关注
咱们一块分享AI学习与发展的干货
如转载,请后台留言,遵守转载规范
|