Performance tuning in Python

组内同事下午做了个关于python的小的share,觉得讲的还不错,将其中的Performance Tuning部分摘抄出来,供参考。

首先,谨记高德纳老先生的名言:过早优化是万恶之源(Premature optimization is the root of all evil.)

性能调优分问如下几步:

  1. Find bottlenecks
  2. Use better algorithms
  3. Use faster tools
  4. Write optimized code
  5. Write your own python module
  6. Parallezie the compution

第一步是找出程序运行的性能瓶颈所在。python里提供了相应的工具,如Profile和cProfile。

这里先给出一份描述更详尽的文章:关于Python Profiles性能分析器http://kb.cnblogs.com/a/2337112/

Profile是一个纯的python模块,而cProfile是用C语言写的一个python拓展。这里使用cProfile。

(注:cProfile可能需要自行安装sudo apt-get install python-profiler)。

给出一个待分析的程序profiler_demo.py:

 1 #!/usr/bin/env python
 2 
 3 above_limit = 10000001
 4 def func1():
 5     s = 0
 6     for i in xrange(above_limit):
 7         s += i
 8 
 9 def func2():
10     s = sum(range(above_limit))
11 
12 def func3():
13     s = sum(xrange(above_limit))
14 
15 func1()
16 func2()
17 func3()

在命令行执行python -m cProfile profiler_demo.py ,即可在运行程序的同时,得出程序的性能分析结果。

liuhao@liuhao-Lenovo:~/program/python$ python -m cProfile profiler_demo.py 
         8 function calls in 1.083 CPU seconds

   Ordered by: standard name

   ncalls  tottime  percall  cumtime  percall filename:lineno(function)
        1    0.000    0.000    0.101    0.101 profiler_demo.py:12(func3)
        1    0.000    0.000    1.083    1.083 profiler_demo.py:3(<module>)
        1    0.377    0.377    0.377    0.377 profiler_demo.py:4(func1)
        1    0.153    0.153    0.604    0.604 profiler_demo.py:9(func2)
        1    0.000    0.000    0.000    0.000 {method 'disable' of '_lsprof.Profiler' objects}
        1    0.289    0.289    0.289    0.289 {range}
        2    0.263    0.131    0.263    0.131 {sum}

对结果进行简单说明:

1.从第一行可以看出,此Python脚本共包括8个函数调用,一共花费的CPU时间为1.083秒;

2.ncalls为函数调用次数,tottime为函数本身不包含调用其他函数的执行时间,cumtime为总体函数的调用执行时间;

3.cProfiler是基于lsprof的,从输出中标红的部分也可以看到;

4.按照结果,我们知道func3是执行最快的;

第二步,是选用更好的算法,在数据规模很大的时候,能够使用O(NlogN)的算法,就不要选用O(N^2)的算法;能够用(1+100)*100/2,就不要用sum(xrange(101));

这里再给出一个使用Python提供的Decorator,利用空间换时间,计算Fibonacci数列的例子:

程序如下:

 1 def fib_nocache(n):
 2     if n == 0 or n == 1:
 3         return 1
 4     return fib_nocache(n-2) + fib_nocache(n-1)
 5 
 6 def cache(func):
 7     c = {}
 8     def _(n):
 9         r = c.get(n)
10         if r is None:
11             r = c[n] = func(n)
12         return r
13     return _
14 
15 @cache
16 def fib_cache(n):
17     if n == 0 or n == 1:
18         return 1
19     return fib_cache(n-2) + fib_cache(n-1)
20        
21 fib_nocache(32)
22 fib_cache(32)

性能分析结果如下:

         7049317 function calls (69 primitive calls) in 2.305 CPU seconds

   Ordered by: standard name

   ncalls  tottime  percall  cumtime  percall filename:lineno(function)
        1    0.000    0.000    2.305    2.305 profiler_fib.py:1(<module>)
7049155/1    2.305    0.000    2.305    2.305 profiler_fib.py:1(fib_nocache)
     33/1    0.000    0.000    0.000    0.000 profiler_fib.py:15(fib_cache)
        1    0.000    0.000    0.000    0.000 profiler_fib.py:6(cache)
     63/1    0.000    0.000    0.000    0.000 profiler_fib.py:8(_)
        1    0.000    0.000    0.000    0.000 {method 'disable' of '_lsprof.Profiler' objects}
       63    0.000    0.000    0.000    0.000 {method 'get' of 'dict' objects}

之前没有接触过Decorator,看到类似的写法还是蛮受用的。

第三步,使用更好的工具。这里主要指Python中的一些惯用法,比如:

  1. xrange()比range()更快;
  2. itertools.imap()比map()更快;
  3. dict.iteritems()比dict.items更快;
  4. for i, item in enumerate(seq)比 for i in range(len(seq))更快;
  5. etc

第四步,写更好的代码,将好的算法和好的工具结合到一块;

第五步和第六步,一个是将关键的模块用C来重写,一个是将原本串行的程序改成并行执行,都值得再单独写一篇博客,以后有空再慢慢写吧。

原文地址:https://www.cnblogs.com/liuhao/p/2612188.html