当前位置: 首页 > 后端技术 > Python

超干分享!如何提高Python的运行速度?

时间:2023-03-26 14:58:38 Python

Python一直受到全世界程序员的喜爱,但它仍然受到一些人的批评,其中一个原因是它被认为是缓慢的。一个特定的程序(无论使用何种编程语言)运行得快还是慢在很大程度上取决于编写它的开发人员的素质以及他们编写优化和高效代码的能力。Medium上有位小哥详细讲解了如何让Python提速30%,从而证明代码运行慢不是Python的问题,而是代码本身的问题。01时序分析在开始进行任何优化之前,我们首先需要找出代码的哪些部分使整个程序变慢。有时程序的问题很明显,但如果你此时不知道问题出在哪里,这里有一些可能的选择:注意:这是我将用于演示的程序,它将进行指数计算#slow_program。pyfromdecimalimport*defexp(x):getcontext().prec+=2i,lasts,s,fact,num=0,0,1,1,1而s!=lasts:lasts=si+=1fact*=inum*=xs+=num/factgetcontext().prec-=2return+sexp(Decimal(150))exp(Decimal(400))exp(Decimal(3000))首先是最简约的“配置文件”,最简单最偷懒的方法——Unix时间命令。~$timepython3.8slow_program.pyreal0m11,058suser0m11,050ssys0m0,008s如果你只知道整个程序的运行时间就足够了,但通常是不够的。最详细的配置文件是另一个命令cProfile,但是它提供的信息太详细了。~$python3.8-mcProfile-stimeslow_program.py在11.081秒内进行了1297次函数调用(1272次原语调用)排序依据:内部时间ncallstottimepercallcumtimepercall文件名:lineno(函数)311.0793.69311.0793.693slow_program.py:4(exp)10.0000.0000.0020.002{内置方法_imp.create_dynamic}4/10.0000.00011.08111.081{内置方法builtins.exec}60.0000.0000.0000.000{内置方法__new9d12x0处的对象类型}60.0000.0000.0000.000abc.py:132(__New__)230.0000.0000.0000.0000.000_weakrefset.py:36(__INIT__)2450.0000.0000.0000.0000.0000.000{内置方法bectionins.getAttr}20.0000.0000.0000.0000.0000.0000.0000.000{方法marshal.loads}100.0000.0000.0000.000:1233(find_spec)8/40.0000.0000.0000.000abc.py:196(__________________________________________________________________________________________class__}10.0000.0000.0000.0000.0000.0000.0000.0000.0000.000__INIT__INIT__。namedtuple)480.0000.0000.0000.000<冻结导入库._bootstrap_external>:57(_path_join)480.0000.0000.0000.000<冻结导入库._bootstrap_external>:59()10.0000.008111.00111.001慢_模块)这里我们使用cProfile模块和时间参数运行测试脚本,以便按内部时间(cumtime)对行进行排序这给了我们很多信息,您在上面看到的行大约是实际输出的10%。由此可以看出exp函数是罪魁祸首,现在我们可以更详细地查看时序和性能分析。为特定函数计时现在我们知道应该关注哪里,我们可能希望在不测量其余代码的情况下为慢函数计时。为此,我们可以使用一个简单的包装器:deftimeit_wrapper(func):@wraps(func)defwrapper(*args,**kwargs):start=time.perf_counter()#或者,您可以使用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))returnfunc_return_valreturnwrapper然后可以将装饰器应用于被测函数,如下所示:@timeit_wrapperdefexp(x):...print('{0:<10}{1:<8}{2:^8}'.format('module','function','time'))exp(Decimal(150))exp(Decimal(400))exp(Decimal(3000))这给了我们这样的输出:~$python3.8slow_program.pymodulefunctiontime__main__.exp:0.003267502994276583__main__.exp:0.038535295985639095__main__.exp:11.728486061969306需要考虑的一件事是我们实际要测量的时间time包提供了两个函数,time.perf_counter.process_time。它们的区别在于perf_counter返回的绝对值包含了你的Python程序进程没有运行的时间,所以可能会受到计算机负载的影响。另一方面,process_time仅返回用户时间(不包括系统时间),这只是您的进程时间。02提速!让Python程序运行得更快是有趣的部分!我不打算展示可以解决您的性能问题的技巧和代码,更多的是关于想法和策略,使用这些想法和策略会对性能产生巨大影响,在某些情况下,可以提高30%。这在使用内置数据类型时很明显。内置数据类型非常快,特别是与我们的自定义类型(如树或链表)相比。这主要是因为builtins是用C实现的,所以我们无法真正匹配用Python编码时的速度。使用lru_cache进行缓存/记忆我已经在之前的博客文章中展示了这一点,但我认为值得用一个简单的例子来重复它:importfunctoolsimporttime#cachingupto12differentresults@functools.lru_cache(maxsize=12)defslow_func(x):time.sleep(2)#模拟长时间计算returnxslow_func(1)#...在得到结果之前等待2秒slow_func(1)#已经缓存-结果立即返回!slow_func(3)#...等待获得结果前2秒上面的函数使用time.sleep来模拟繁重的计算。第一次使用参数1调用时,它将等待2秒,然后返回结果。再次调用时,结果已经缓存了,所以会跳过函数体,直接返回结果。有关更多实际示例,请参阅以前的博客文章。使用局部变量这与在每个范围内查找变量的速度有关,因为它不只是使用局部或全局变量。事实上,即使在函数的局部变量(最快)、类级属性(例如self.name-较慢)和全局变量(例如导入函数)之间,如time.time(最慢),查找实际上也是不同的。您可以通过使用看似不必要的分配来提高性能,如下所示:#Example#1classFastClass:defdo_stuff(self):temp=self.value#thisspeeduplookupinloopforiinrange(10000):...#Do这里有`temp`的东西#Example#2importrandomdeffast_function():r=random.randomforiinrange(10000):print(r())#在这里调用`r()`比全局random.random()使用函数更快,这似乎违反直觉,因为调用函数会在堆栈并从函数返回中产生开销,但这与前一点有关。如果您只是将整个代码放在一个文件中而不将其放在一个函数中,那么由于全局变量,它的运行速度会慢得多。因此,你可以通过将整个代码包装在一个main函数中并调用它一次来加速你的代码,像这样:defmain():...#你之前所有的全局代码main()不访问属性可能会让你的程序成为另一回事最慢的是点运算符(.),它在获取对象属性时使用。此运算符使用__getattribute__来触发字典查找,这会在代码中产生额外的开销。那么,如何才能真正避免(限制)它的使用呢?#慢:importredefslow_func():foriinrange(10000):re.findall(regex,line)#慢!#快:fromreimportfindalldeffast_func():foriinrange(10000):findall(regex,line)#更快!当心字符串当使用模数(%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(s=s,t=t)#慢!generator并不是天生就更快,因为它们被允许进行惰性计算,节省内存而不是时间。但是,节省的内存可能会使您的程序实际运行得更快。你是怎么做到的?如果你有一个大数据集,而不使用生成器(迭代器),那么数据可能会溢出CPUL1缓存,这将大大减慢在内存中查找值的速度。就性能而言,CPU能够将其正在处理的所有数据尽可能多地保留在缓存中非常重要。您可以观看RaymondHettingers解决这些问题的视频。03结语优化的第一法则是不优化。但是,如果您真的需要,那么我希望上面的这些提示可以帮助到您。但是,在优化代码时要小心,因为它最终可能会使代码难以阅读并因此难以维护,这可能会超过优化的好处。最近为初学者整理了数百G的Python学习资料,包括电子书、教程、源码等,免费分享给大家!想上“Python编程学习圈”,发“J”免费领取