像老大一样优化 Python

简介: 如果不首先想想这句Knuth的名言,就开始进行优化工作是不明智的。可是,你很快写出来加入一些特性的代码,可能会很丑陋,你需要注意了。这篇文章就是为这时候准备的。那么接下来就是一些很有用的工具和模式来快速优化Python。它的主要目的很简单:尽快发现瓶颈,修复它们并且确认你修复了它们。

我们应该忘掉一些小的效率问题,在 97% 的情况下是这么说的:过早优化是万恶之源。—— Donald Knuth

如果不首先想想这句Knuth的名言,就开始进行优化工作是不明智的。可是,你很快写出来加入一些特性的代码,可能会很丑陋,你需要注意了。这篇文章就是为这时候准备的。

那么接下来就是一些很有用的工具和模式来快速优化Python。它的主要目的很简单:尽快发现瓶颈,修复它们并且确认你修复了它们。


写一个测试

在你开始优化前,写一个高级测试来证明原来代码很慢。你可能需要采用一些最小值数据集来复现它足够慢。通常一两个显示运行时秒的程序就足够处理一些改进的地方了。

有一些基础测试来保证你的优化没有改变原有代码的行为也是很必要的。你也能够在很多次运行测试来优化代码的时候稍微修改这些测试的基准。

那么现在,我们来来看看优化工具把。


简单的计时器

计时器很简单,这是一个最灵活的记录执行时间的方法。你可以把它放到任何地方并且副作用很小。运行你自己的计时器非常简单,并且你可以将其定制,使它以你期望的方式工作。例如,你个简单的计时器如下:

import time

deftimefunc(f):

   deff_timer(*args, **kwargs):

       start = time.time()

       result = f(*args, **kwargs)

       end = time.time()

       print f.__name__, 'took', end - start, 'time'

       return result

   return f_timer

defget_number():

   for x in xrange(5000000):

       yield x

@timefunc

defexpensive_function():

   for x in get_number():

       i = x ^ x ^ x

   return'some result!'

# prints "expensive_function took 0.72583088875 seconds"

result = expensive_function()

当然,你可以用上下文管理来让它功能更加强大,添加一些检查点或者一些其他的功能:

import time

classtimewith():

   def__init__(self, name=''):

       self.name = name

       self.start = time.time()

   @property

   defelapsed(self):

       return time.time() - self.start

   defcheckpoint(self, name=''):

       print'{timer} {checkpoint} took {elapsed} seconds'.format(

           timer=self.name,

           checkpoint=name,

           elapsed=self.elapsed,

       ).strip()

   def__enter__(self):

       return self

   def__exit__(self, type, value, traceback):

       self.checkpoint('finished')

       pass

defget_number():

   for x in xrange(5000000):

       yield x

defexpensive_function():

   for x in get_number():

       i = x ^ x ^ x

   return'some result!'

# prints something like:

# fancy thing done with something took 0.582462072372 seconds

# fancy thing done with something else took 1.75355315208 seconds

# fancy thing finished took 1.7535982132 seconds

with timewith('fancy thing') as timer:

   expensive_function()

   timer.checkpoint('done with something')

   expensive_function()

   expensive_function()

   timer.checkpoint('done with something else')

# or directly

timer = timewith('fancy thing')

expensive_function()

timer.checkpoint('done with something')

计时器还需要你做一些挖掘。包装一些更高级的函数,并且确定瓶颈在哪,然后深入的函数里,能够不停的重现。当你发现一些不合适的代码,修复它,然后测试一遍以确认它被修复了。

一些小技巧:不要忘了好用的timeit模块!它对小块代码做基准测试而不是实际调查更加有用。

  • Timer 优点:很容易理解和实现。也非常容易在修改后进行比较。对于很多语言都适用。
  • Timer 缺点:有时候对于非常复杂的代码有点过于简单,你可能会花更多时间放置或移动引用代码而不是修复问题!


内建优化器

启用内建的优化器就像是用一门大炮。它非常强大,但是有点不太好用,使用和解释起来比较复杂。

你可以了解更多关于profile模块的东西,但是它的基础是非常简单的:你能够启用和禁用优化器,而且它能打印所有的函数调用和执行时间。它能给你编译和打印出输出。一个简单的装饰器如下:

import cProfile

defdo_cprofile(func):

   defprofiled_func(*args, **kwargs):

       profile = cProfile.Profile()

       try:

           profile.enable()

           result = func(*args, **kwargs)

           profile.disable()

           return result

       finally:

           profile.print_stats()

   return profiled_func

defget_number():

   for x in xrange(5000000):

       yield x

@do_cprofile

defexpensive_function():

   for x in get_number():

       i = x ^ x ^ x

   return'some result!'

# perform profiling

result = expensive_function()

在上面代码的情况下,你应该看到有些东西在终端打印出来,打印的内容如下:

5000003 function calls in 1.626 seconds

  Ordered by: standard name

  ncalls  tottime  percall  cumtime  percall filename:lineno(function)

 5000001    0.571    0.000    0.571    0.000 timers.py:92(get_number)

       1    1.055    1.055    1.626    1.626 timers.py:96(expensive_function)

       1    0.000    0.000    0.000    0.000 {method 'disable' of '_lsprof.Profiler' objects}

你可以看到,它给出了不同函数的调用次数,但它遗漏了一些关键的信息:是哪个函数让运行这么慢?

可是,这对于基础优化来说是个好的开始。有时候甚至能用更少的精力找到解决方案。我经常用它来在深入挖掘究竟是哪个函数慢或者调用次数过多之前来调试程序。

内建优点:没有额外的依赖并且非常快。对于快速的高等级检查非常有用。

内建缺点:信息相对有限,需要进一步的调试;报告有点不太直接,尤其是对于复杂的代码。


Line Profiler

如果内建的优化器是一门大炮,那么line profiler可以看作是一门离子加农炮。它非常的重量级和强大。

在这个例子里,我们会用非常棒的line_profiler库。为了容易使用,我们会再次用装饰器包装一下,这种简单的方法也可以防止把它放在生产代码里。

try:

   from line_profiler import LineProfiler

   defdo_profile(follow=[]):

       definner(func):

           defprofiled_func(*args, **kwargs):

               try:

                   profiler = LineProfiler()

                   profiler.add_function(func)

                   for f in follow:

                       profiler.add_function(f)

                   profiler.enable_by_count()

                   return func(*args, **kwargs)

               finally:

                   profiler.print_stats()

           return profiled_func

       return inner

except ImportError:

   defdo_profile(follow=[]):

       "Helpful if you accidentally leave in production!"

       definner(func):

           defnothing(*args, **kwargs):

               return func(*args, **kwargs)

           return nothing

       return inner

defget_number():

   for x in xrange(5000000):

       yield x

@do_profile(follow=[get_number])

defexpensive_function():

   for x in get_number():

       i = x ^ x ^ x

   return'some result!'

result = expensive_function()

如果你运行上面的代码,你就可以看到一下的报告:

Timer unit: 1e-06 s

File: test.py

Function: get_number at line 43

Total time: 4.44195 s

Line #      Hits         Time  Per Hit   % Time  Line Contents

==============================================================

   43                                           def get_number():

   44   5000001      2223313      0.4     50.1      for x in xrange(5000000):

   45   5000000      2218638      0.4     49.9          yield x

File: test.py

Function: expensive_function at line 47

Total time: 16.828 s

Line #      Hits         Time  Per Hit   % Time  Line Contents

==============================================================

   47                                           def expensive_function():

   48   5000001     14090530      2.8     83.7      for x in get_number():

   49   5000000      2737480      0.5     16.3          i = x ^ x ^ x

   50         1            0      0.0      0.0      return 'some result!'

你可以看到,有一个非常详细的报告,能让你完全洞悉代码运行的情况。和内置的 cProfiler 不同,它能计算话在语言核心特性的时间,比如循环和导入并且给出在不同的行花费的时间。

这些细节能让我们更容易理解函数内部。如果你在研究某个第三方库,你可以直接将其导入并加上装饰器来分析它。

一些小技巧:只装饰你的测试函数并将问题函数作为接下来的参数。

  • Line Profiler 优点:有非常直接和详细的报告。能够追踪第三方库里的函数。
  • Line Profiler 缺点:因为它会让代码比真正运行时慢很多,所以不要用它来做基准测试。这是额外的需求。

总结和最佳实践

你应该用更简单的工具来对测试用例进行根本的检查,并且用更慢但能显示更多细节的line_profiler来深入到函数内部。

九成情况下,你可能会发现在一个函数里循环调用或一个错误的数据结构消耗了90%的时间。一些调整工具是非常适合你的。

如果你仍然觉得这太慢,而是用一些你自己的秘密武器,如比较属性访问技术或调整平衡检查技术。你也可以用如下的方法:

1.忍受缓慢或者缓存它们

2.重新思考整个实现

3.更多使用优化的数据结构

4.写一个C扩展

注意了,优化代码是种罪恶的快感!用合适的方法来为你的Python代码加速很有意思,但是注意不要破坏了本身的逻辑。可读的代码比运行速度更重要。先把它缓存起来再进行优化其实更好。

相关文章
|
24天前
|
机器学习/深度学习 算法 安全
【PSO-LSTM】基于PSO优化LSTM网络的电力负荷预测(Python代码实现)
【PSO-LSTM】基于PSO优化LSTM网络的电力负荷预测(Python代码实现)
|
26天前
|
调度 Python
微电网两阶段鲁棒优化经济调度方法(Python代码实现)
微电网两阶段鲁棒优化经济调度方法(Python代码实现)
|
25天前
|
机器学习/深度学习 算法 调度
【EI复现】基于深度强化学习的微能源网能量管理与优化策略研究(Python代码实现)
【EI复现】基于深度强化学习的微能源网能量管理与优化策略研究(Python代码实现)
|
3月前
|
监控 大数据 API
Python 技术员实践指南:从项目落地到技术优化
本内容涵盖Python开发的实战项目、技术攻关与工程化实践,包括自动化脚本(日志分析系统)和Web后端(轻量化API服务)两大项目类型。通过使用正则表达式、Flask框架等技术,解决日志分析效率低与API服务性能优化等问题。同时深入探讨内存泄漏排查、CPU瓶颈优化,并提供团队协作规范与代码审查流程。延伸至AI、大数据及DevOps领域,如商品推荐系统、PySpark数据处理和Airflow任务编排,助力开发者全面提升从编码到架构的能力,积累高并发与大数据场景下的实战经验。
Python 技术员实践指南:从项目落地到技术优化
|
2月前
|
存储 监控 算法
基于 Python 跳表算法的局域网网络监控软件动态数据索引优化策略研究
局域网网络监控软件需高效处理终端行为数据,跳表作为一种基于概率平衡的动态数据结构,具备高效的插入、删除与查询性能(平均时间复杂度为O(log n)),适用于高频数据写入和随机查询场景。本文深入解析跳表原理,探讨其在局域网监控中的适配性,并提供基于Python的完整实现方案,优化终端会话管理,提升系统响应性能。
67 4
|
4月前
|
网络协议 API 开发者
分析http.client与requests在Python中的性能差异并优化。
合理地选择 `http.client`和 `requests`库以及在此基础上优化代码,可以帮助你的Python网络编程更加顺利,无论是在性能还是在易用性上。我们通常推荐使用 `requests`库,因为它的易用性。对于需要大量详细控制的任务,或者对性能有严格要求的情况,可以考虑使用 `http.client`库。同时,不断优化并管理员连接、设定合理超时和重试都是提高网络访问效率和稳定性的好方式。
119 19
|
2月前
|
数据采集 机器学习/深度学习 边缘计算
Python爬虫动态IP代理报错全解析:从问题定位到实战优化
本文详解爬虫代理设置常见报错场景及解决方案,涵盖IP失效、403封禁、性能瓶颈等问题,提供动态IP代理的12种核心处理方案及完整代码实现,助力提升爬虫系统稳定性。
144 0
|
6月前
|
机器学习/深度学习 算法 调度
【强化学习】基于深度强化学习的微能源网能量管理与优化策略研究【Python】
本项目基于深度Q网络(DQN)算法,通过学习预测负荷、可再生能源输出及分时电价等信息,实现微能源网的能量管理与优化。程序以能量总线模型为基础,结合强化学习理论,采用Python编写,注释清晰,复现效果佳。内容涵盖微能源网系统组成、Q学习算法原理及其实现,并提供训练奖励曲线、发电单元功率、电网交互功率和蓄电池调度等运行结果图表,便于对照文献学习与应用。
|
6月前
|
缓存 并行计算 数据处理
全面提升Python性能的十三种优化技巧
通过应用上述十三种优化技巧,开发者可以显著提高Python代码的执行效率和性能。每个技巧都针对特定的性能瓶颈进行优化,从内存管理到并行计算,再到使用高效的数值计算库。这些优化不仅能提升代码的运行速度,还能提高代码的可读性和可维护性。希望这些技巧能帮助开发者在实际项目中实现更高效的Python编程。
381 22
|
7月前
|
关系型数据库 数据库 数据安全/隐私保护
云数据库实战:基于阿里云RDS的Python应用开发与优化
在互联网时代,数据驱动的应用已成为企业竞争力的核心。阿里云RDS为开发者提供稳定高效的数据库托管服务,支持多种数据库引擎,具备自动化管理、高可用性和弹性扩展等优势。本文通过Python应用案例,从零开始搭建基于阿里云RDS的数据库应用,详细演示连接、CRUD操作及性能优化与安全管理实践,帮助读者快速上手并提升应用性能。

热门文章

最新文章

推荐镜像

更多