下载安卓APP箭头
箭头给我发消息

客服QQ:3315713922

编程语言Python性能提升30%

作者:佚名     来源: 机器学习算法与Python学习点击数:797发布时间: 2020-02-10 14:13:14

标签: 编程语言视频Python视频Python视频教程

大神带你学编程,欢迎选课

教你几招,Python性能提升30%!在过去的几十年间,大量的编程语言被发明、被取代、被修改或组合在一起。尽管人们多次试图创造一种通用的程序设计语言,却没有一次尝试是成功的。

之所以有那么多种不同的编程语言存在的原因是,编写程序的初衷其实也各不相同;新手与老手之间技术的差距非常大,而且有许多语言对新手来说太难学;还有,不同程序之间的运行成本(runtime cost)各不相同。

本文主要分享如何提升Python性能的几个使用方法!

时序分析

优化之前,首先要找到是哪部分代码拖慢了整个程序的运行。有时候程序的"瓶颈"不是很明显,如果找不到,以下是一些建议以供参考:

注意:这是一个计算e的x次幂的演示程序(出自Python文档):

  1. # slow_program.py  
  2. from decimal import*  
  3. defexp(x):  
  4.     getcontext().prec +=2  
  5.     i, lasts, s, fact, num =0, 0, 1, 1, 1  
  6.     while s != lasts:  
  7.         lasts = s  
  8.         i +=1  
  9.         fact *= i  
  10.         num *= x  
  11.         s += num / fact  
  12.     getcontext().prec -=2  
  13.     return+s  
  14. exp(Decimal(150))  
  15. exp(Decimal(400))  
  16. exp(Decimal(3000)) 

在GitHub上查看rawslow_program.py全部代码

最省力的“性能分析”

首先,最简单且最省力的解决方案是使用Unix的time命令: 

  1. ~ $ time python3.8  slow_program.py  
  2. real     0m11,058s  
  3. user     0m11,050s  
  4. sys      0m0,008s 

在GitHub上查看rawbase_time.shell全部代码

如果只是给整个程序计时,它很有用,但还不足够……

最详细的性能分析

性能分析的另一方法是cProfile,从中能得到很大的信息量:

  1. ~ $ python3.8 -m  cProfile -s time slow_program.py  
  2.          1297 function calls (1272 primitive  calls) in 11.081 seconds  
  3.    Ordered by: internal time  
  4.    ncalls   tottime  percall  cumtime   percall filename:lineno(function)  
  5.         3    11.079    3.693   11.079     3.693 slow_program.py:4(exp)  
  6.         1     0.000    0.000    0.002     0.002 {built-in method _imp.create_dynamic}  
  7.       4/1     0.000    0.000   11.081    11.081 {built-in method builtins.exec}  
  8.         6     0.000    0.000    0.000     0.000 {built-in method __new__ of type object at 0x9d12c0}  
  9.         6     0.000    0.000    0.000     0.000 abc.py:132(__new__)  
  10.        23     0.000    0.000    0.000     0.000 _weakrefset.py:36(__init__)  
  11.       245     0.000    0.000    0.000     0.000 {built-in method builtins.getattr}  
  12.         2     0.000    0.000    0.000     0.000 {built-in method marshal.loads}  
  13.        10     0.000    0.000    0.000     0.000 <frozen importlib._bootstrap_external>:1233(find_spec)  
  14.       8/4     0.000    0.000    0.000     0.000 abc.py:196(__subclasscheck__)  
  15.        15     0.000    0.000    0.000     0.000 {built-in method posix.stat}  
  16.         6     0.000    0.000    0.000     0.000 {built-in method builtins.__build_class__}  
  17.         1     0.000    0.000    0.000     0.000 __init__.py:357(namedtuple)  
  18.        48     0.000    0.000    0.000     0.000 <frozen importlib._bootstrap_external>:57(_path_join)  
  19.        48     0.000    0.000    0.000     0.000 <frozen importlib._bootstrap_external>:59(<listcomp>)  
  20.         1     0.000    0.000   11.081    11.081 slow_program.py:1(<module>)  
  21. ... 

在GitHub上查看rawcprofile.shell全部代码

这里用cProfile模块和time参数运行测试脚本,以便按内部时间(cumtime)对行进行排序。从中可以得到很多信息,以上所列结果约为实际输出的10%。由此可见,exp函数就是拖慢程序的“罪魁祸首”(太神奇啦!),现在看看更详尽的时序和性能分析......

对特定函数计时

已经知道拖慢程序运行的函数,下一步可使用简单的修饰器,专门对该函数计时,不测量其余代码。如下所示:

  1. deftimeit_wrapper(func):  
  2.     @wraPS(func)  
  3.     defwrapper(*args, **kwargs):  
  4.         start =  time.perf_counter()  # Alternatively, you  can use time.process_time()  
  5.         funcfunc_return_val = func(*args, **kwargs)  
  6.         end = time.perf_counter()  
  7.         print( {0:<10}.{1:<8} : {2:<8} .format(func.__module__, func.__name__, end - start))  
  8.         return func_return_val  
  9.     return wrapper 

在GitHub上查看rawtimeit_decorator.py全部代码

该修饰器可以应用于功能测试,如下所示:

  1. @timeit_wrapper  
  2. defexp(x):  
  3.     ...  
  4. print( {0:<10}{1:<8}{2:^8} .format( module ,  function ,  time ))  
  5. exp(Decimal(150))  
  6. exp(Decimal(400))  
  7. exp(Decimal(3000)) 

在GitHub上查看rawtimeit_decorator_usage.py全部代码

输出如下: 

  1. ~ $ python3.8  slow_program.py  
  2. module     function   time     
  3. __main__  .exp       :0.003267502994276583  
  4. __main__  .exp       :0.038535295985639095  
  5. __main__  .exp       : 11.728486061969306 

在GitHub上查看rawrun_with_timeit_decorator.shell全部代码

要考虑的一个问题是实际/想要测量的时间类型是什么。Time程序包提供了time.perf_counter和time.process_time。两者的区别是:perf_counter返回绝对值,其中包括Python程序进程未运行时的时间,因此可能会受计算机负载的影响;而process_time仅返回用户时间(不包括系统时间),这仅是程序的运行时间。

加快程序运行速度

编程语言Python性能提升30%_编程语言视频_Python视频_Python视频教程_课课家

图源:Unsplash

这是全文有趣的部分,关于如何加快Python的程序运行速度。我并没有列出一些可以奇妙解决性能问题的小技巧或代码段,而是涉及一般性的构想和策略,它们能极大地提高性能,某些情况下甚至能将性能提高30%。

使用内置数据类型

显而易见,内置数据类型运行很快,尤其是与自定义类型(例如树或链表)相比。主要是因为内置程序是用C语言实现的,远超过用Python编码的运行速度。

使用lru_cache缓存/记忆 

我已经在上一篇博文中讲过这块内容,但在此还是要用简单的示例说明:

  1. import functools  
  2. import time  
  3. # caching up to 12  different results  
  4. @functools.lru_cache(maxsize=12)  
  5. defslow_func(x):  
  6.     time.sleep(2)  # Simulate long computation  
  7.     return x  
  8. slow_func(1)  # ... waiting for 2 sec before getting  result  
  9. slow_func(1)  # already cached - result returned  instantaneously!  
  10. slow_func(3)  # ... waiting for 2 sec before getting  result 

在GitHub上查看rawlru_cache.py全部代码

以上函数使用time.sleep模拟大量运算。第一次使用参数1调用该函数时,返回结果需要2秒。再次调用时,结果已被缓存,因此会跳过函数主体并立即返回结果。更多内容请参见此处。

使用局部变量

这与在每个作用域中查找变量的速度有关。我用了“每个作用域”这个字眼,因为它不仅仅是“使用局部变量还是全局变量”的问题。实际上,即使在函数的局部变量(最快)、类级属性(如self.name-较慢)和全局变量(如导入的函数,time.time-最慢)之间,查找速度也有所不同。

可以通过运行无用的任务来提高性能,如下所示:

  1. #  Example #1  
  2. classFastClass:  
  3.     defdo_stuff(self):  
  4.         temp =self.value  # this speeds up lookup in loop  
  5.         for i inrange(10000):  
  6.             ...  # Do something with `temp` here  
  7. #  Example #2  
  8. import random  
  9. deffast_function():  
  10.     r = random.random  
  11.     for i inrange(10000):  
  12.         print(r())  # calling `r()` here, is faster than  global random.random() 

在GitHub上查看rawlocal_vars.py全部代码

使用函数(Function)

这怎么和假想的不同?理论上调用函数不是会将更多的东西放到堆栈上,加大返回结果的负担吗?但实际上,使用函数确实能加快运行速度,这与前一点有关。将整个代码放在一个文件中而非函数中,它是全局变量而非局部变量,运行速度就会慢得多。因此,可以将整个代码包裹在main函数中并通过一次调用来加速代码,如下所示:

  1. defmain():  
  2.     ...  # All your previously global code  
  3. main() 

在GitHub上查看rawglobal_vars.py全部代码

避免访问属性(Attribute)

可能拖慢程序的一个原因是使用点运算符(.)访问对象属性。该运算符通过使用__getattribute__方法触发了字典查找,使代码产生额外负担。那么,如何避免或减少属性访问? 

  1. #  Slow:  
  2. import re  
  3. defslow_func():  
  4.     for i inrange(10000):  
  5.         re.findall(regex, line)  # Slow!  
  6. #  Fast:  
  7. from re import findall  
  8. deffast_func():  
  9.     for i inrange(10000):  
  10.         findall(regex, line)  # Faster! 

在GitHub上查看rawimports.py全部代码

当心使用字符串

在循环里使用格式符(%s)或.format()时,字符串操作可能会变得非常慢。有没有更好的选择?Raymond Hettinger在最近发布的推文中提到:唯一应该使用的是f-string(格式化字符串常量),它是最易读、最简洁且最快捷的方法。根据这篇推文,下面列出了可用的方法(由快到慢):

  1. f {s}{t}   # Fast!  
  2. s +    + t  
  3.    .join((s, t))  
  4.  %s %s % (s, t)  
  5.  {} {} .format(s, t)  
  6. Template( $s $t ).substitute(ss=s, tt=t)  # Slow! 

在GitHub上查看rawstrings.py全部代码

本质上,生成器并没有变得更快,因为它在设计上允许延迟计算以节省内存而非节约时间。然而节省的内存也可以加快程序实际运行速度。怎么做?如果有一个很大的数据集且不使用生成器(迭代器),那么数据可能会溢出CPU的L1 cache(1级缓存),这将大大减慢内存的查找速度。

在性能方面,极重要的一点是:CPU可以将正在处理的所有数据尽可能地保存在缓存中。

图源:Unsplash

结语

优化的首要规则就是“不优化”。

若真的有必要优化,那我希望这些技巧会有所帮助。

但是,优化代码时一定要小心,因为优化的结果可能是代码难以阅读进而难以维护,这就得不偿失了。

最后,希望大家能搭上python号火箭,编码越来越快!

编程语言往往使程序员能够比使用机器语言更准确地表达他们所想表达的目的。对那些从事计算机科学的人来说,懂得程序设计语言是十分重要的,因为在当今所有的计算都需要程序设计语言才能完成。

赞(8)
踩(0)
分享到:
华为认证网络工程师 HCIE直播课视频教程