像老大一样优化Python(1)


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

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

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

写一个测试

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

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

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

简单的计时器

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

  1. import time 
  2.   
  3. def timefunc(f): 
  4.     def f_timer(*args, **kwargs): 
  5.         start = time.time() 
  6.         result = f(*args, **kwargs) 
  7.         end = time.time() 
  8.         print f.__name__, 'took', end - start, 'time' 
  9.         return result 
  10.     return f_timer 
  11.   
  12. def get_number(): 
  13.     for x in xrange(5000000): 
  14.         yield x 
  15.   
  16. @timefunc 
  17. def expensive_function(): 
  18.     for x in get_number(): 
  19.         i = x ^ x ^ x 
  20.     return 'some result!' 
  21.   
  22. # prints "expensive_function took 0.72583088875 seconds" 
  23. result = expensive_function() 

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

  1. import time 
  2.   
  3. class timewith(): 
  4.     def __init__(self, name=''): 
  5.         self.name = name 
  6.         self.start = time.time() 
  7.   
  8.     @property 
  9.     def elapsed(self): 
  10.         return time.time() - self.start 
  11.   
  12.     def checkpoint(self, name=''): 
  13.         print '{timer} {checkpoint} took {elapsed} seconds'.format( 
  14.             timer=self.name, 
  15.             checkpoint=name, 
  16.             elapsed=self.elapsed, 
  17.         ).strip() 
  18.   
  19.     def __enter__(self): 
  20.         return self 
  21.   
  22.     def __exit__(self, type, value, traceback): 
  23.         self.checkpoint('finished'
  24.         pass 
  25.   
  26. def get_number(): 
  27.     for x in xrange(5000000): 
  28.         yield x 
  29.   
  30. def expensive_function(): 
  31.     for x in get_number(): 
  32.         i = x ^ x ^ x 
  33.     return 'some result!' 
  34.   
  35. # prints something like: 
  36. # fancy thing done with something took 0.582462072372 seconds 
  37. # fancy thing done with something else took 1.75355315208 seconds 
  38. # fancy thing finished took 1.7535982132 seconds 
  39. with timewith('fancy thing') as timer: 
  40.     expensive_function() 
  41.     timer.checkpoint('done with something'
  42.     expensive_function() 
  43.     expensive_function() 
  44.     timer.checkpoint('done with something else'
  45.   
  46. # or directly 
  47. timer = timewith('fancy thing'
  48. expensive_function() 
  49. timer.checkpoint('done with something'

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

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

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


评论关闭