[size=1em]感谢dreampuf
Python ProfilingAuthor: dreampuf(http://huangx.in)
Slide: http://www.slideshare.net/dreampuf/python-profiling
URL: [size=1em]http://huangx.in/2012/9/python_profiling.html
Introduction性能分析(Performance analysis 或 profiling)是收集程序运行时信息为手段研究程序行为的分析方法。接下来的分析基于如下版本的快排。
def qks(ls): if not ls : return ls p, ls = ls[0], ls[1:] return qks([i for i in ls if i < p]) + [p] + qks([i for i in ls if i >= p])if __name__ == "__main__": import random ls = [random.randint(0, 100) for i in xrange(10)]time utility, time module, timeit, sys.getsizeof本栏介绍一些系统Profile的工具。
timetime是系统自带的测量工具,能够统计出程序在系统中的耗时。
dreampuf@HX:~/hub/fortest$ time python test_timeit.py 0.143877983093real 0m0.184s #实际总耗时user 0m0.166s #应用耗时sys 0m0.015s #应用期间执行系统指令耗时需要注意的是,real一般而言是指程序总执行时间,但是真正的CPU占用可能只有一小部分,比如在执行sleep 5指令之后,并没有占用CPU 5s,而是将控制权让给了其他并行的程序。
dreampuf@HX:~/hub/fortest$ time sleep 5real 0m5.036suser 0m0.001ssys 0m0.003stime的特点:
- 系统级别耗时(一般而言 real > user + sys)
- 应用耗时和系统调用耗时比率
time module提起Profile,最直接的测量往往如下:
dreampuf@HX:~/hub/fortest$ python test_timeit.py 2.31266021729e-05import timedef time_module(): start = time.time() qks(ls) return time.time() - startCOUNT = 100000print sum([time_module() for i in xrange(COUNT)])/COUNT这当然只是最原始的方式,不过在不变更原有的代码基础之上,能够很快而且灵活的测量出执行时间,所以不失为一种方法。
不过考虑到重用,最好还是封装到自己的常用函数中,Benchmarker算是一个不错的封装,而且提供了比较完善的报表。
time module 特点:
timeit除了自己手写time.time()测量,Python 还提供了内置的timeit模块。这个模块相当于封装了time module的一些测量操作。
from timeit import Timer@Timerdef caller(): qks(ls)print caller.timeit(10000) #0.14427113533除此之外,还能够在解释器环境中,快速执行测量。
In [1]: from timeit import timeitIn [2]: timeit('s.appendleft(3)', ...: 'import collections; s= collections.deque()', number=1000000)Out[2]: 0.1150519847869873In [3]: timeit('s.insert(0, 3)', 's=[]', number=1000000)Out[3]: 392.4638919830322timeit模块的特点:
- 不同平台使用的time不同(Xinux 使用 time.time(), Windows 使用time.clock())
- python -m timeit -s "import collections; s = collections.deque()" -n 1000000 "s.appendleft(3)"
sys.getsizeof除了耗时占用,有的时候我们需要对内存的占用有一个估算,那么内置的sys.getsizeof就能很好的帮助我们。
import sysdef sys_getsizeof(): a = range(1000000) b = xrange(1000000) c = [i for i in xrange(1000000)] print sys.getsizeof(a) #8000072 print sys.getsizeof(b) #40 print sys.getsizeof(c) #8697472上面三条语句分别测量的是:
- 一个列表(Python 2.x)
- 一个迭代器
- 一个通过枚举器输出的列表推导
第一种方式和第三种方式的不同在于,range是系统内置方法,他初始化就确定了列表大小,并不是动态增长的,所以没有动态增长带来的“盈余空间”。可以通过如下方式证明:
In [1]: import sysIn [2]: a = range(1000000)In [3]: b = [i for i in xrange(1000000)]In [4]: sys.getsizeof(a)Out[4]: 8000072In [5]: sys.getsizeof(b)Out[5]: 8697472In [6]: a.append(1)In [7]: b.append(1)In [8]: sys.getsizeof(a) # 由于不是通过动态增长,没有留下额外的空间,增加一个元素需要新开辟一段空间Out[8]: 9000128In [9]: sys.getsizeof(b) # 而b则直接使用之前的盈余空间Out[9]: 8697472当然,你也可以通过源码找到结果(Python2.7_SourceCode/Python/bltinmodule.c r1875)。
下面是对于sys.getsizeof获取的容器大小的一个展示:
class Entity(object): def __init__(self, name, age): self.name = name self.age = agees = [Entity("dreampuf", 100) for i in xrange(1000000)]print sys.getsizeof(es) #8697472print sum([sys.getsizeof(i) for i in es]) #64000000sys.getsizeof的特点:
- 和实际有所出入(Python对象缓存池)
- 容器还是对象
Module: profile, cProfile, hotshot, pystats系统自带的模块或多或少的能够帮助我们解决一些问题,但实际上在我们解决现实问题时有或这或那的不足。
我们就需要其他模块来帮助我们定位问题。
profile,cProfile 以及 hotshot,他们拥有近似的接口,作用也有一样,就是采集运行时的调用信息,将信息打印或者保存为stats格式,供后期Profile。
比如使用cProfile:
from cProfile import Profile as profiledef profile_module(): p = profile() p.enable() qks(ls*100) p.disable() print p.getstats()dreampuf@HX:~/hub/fortest$ python test_timeit.py [_lsprof.profiler_entry(code="<method 'disable' of '_lsprof.Profiler' objects>", callcount=1, reccallcount=0, totaltime=0.0, inlinetime=0.0, calls=None), _lsprof.profiler_entry(code=<code object qks at 0x10a1694b0, file "test_timeit.py", line 6>, callcount=2001, reccallcount=2000, totaltime=0.01724, inlinetime=0.01724, calls=[_lsprof.profiler_subentry(code=<code object qks at 0x10a1694b0, file "test_timeit.py", line 6>, callcount=2000, reccallcount=1998, totaltime=0.017048, inlinetime=0.017048)])]可以看到,直接输出的结果,几乎无法阅读。实际上,还需要借助其他的模块来格式化这些信息:
from cProfile import Profile as profilefrom pstats import Statsdef profile_module(): p = profile() p.snapshot_stats() p.enable() dirs(".") p.disable() p.print_stats(2) # 按照调用累加总耗时累加排序,即将最耗时的函数最优先 p.dump_stats("call.log")这样,我们就能够得到程序的调用结果:
dreampuf@HX:~/hub/fortest$ python test_timeit.py 135259 function calls in 0.071 seconds Ordered by: cumulative time ncalls tottime percall cumtime percall filename:lineno(function) 1 0.002 0.002 0.071 0.071 test_timeit.py:101(dirs) 22 0.005 0.000 0.068 0.003 test_timeit.py:74(main) 10519 0.008 0.000 0.047 0.000 ast.py:203(walk) 10498 0.004 0.000 0.035 0.000 {method 'extend' of 'collections.deque' objects} 20975 0.015 0.000 0.031 0.000 ast.py:173(iter_child_nodes) 21 0.000 0.000 0.014 0.001 ast.py:32(parse) 21 0.014 0.001 0.014 0.001 {compile} 26234 0.009 0.000 0.011 0.000 ast.py:161(iter_fields) 39368 0.008 0.000 0.008 0.000 {isinstance} 1 0.002 0.002 0.004 0.004 collections.py:1(<module>) 15736 0.002 0.000 0.002 0.000 {getattr} 1 0.001 0.001 0.001 0.001 heapq.py:31(<module>) 10498 0.001 0.000 0.001 0.000 {method 'popleft' of 'collections.deque' objects}profile vs. cProfile vs. hotshotprofile模块的特点:
- 行级别调用信息(时间,次数)
- 可以保存执行结果
- 友好的输出信息
Multithread Profiling在Python中,多线程的Profiling,可以通过统一的threading.setprofile接口绑定测量函数。
具体内容可以参见下面的连接。
- threading.setprofile(func)
- sys.setprofile(profilefunc)
- Yet Another Python Profiler, but this time Thread-Aware
Thrid-party modules除了Python自带的模块,其他第三方模块提供了更好的易用功能。
为了更好的展示这些功能,我们使用如下的基准代码:
import reimport osimport astis_chinese = re.compile("[^\x00-\x7f]")mnode = re.compile(">([^<]+?)<")breakline = re.compile("[\r\n]+")title = re.compile("<title>([^<]+)</title>")def main(filename): with open(filename) as f: data = f.read() if filename[-3:] == ".py": try: node = ast.parse(data, filename=filename) except: return for i in ast.walk(node): if not isinstance(i, ast.Str): continue if is_chinese.search(i.s): t = i.s if isinstance(i.s, str) else i.s.encode("u8") #print t.strip()#, i.lineno, filename elif filename[-3:] == "tml": t = title.search(data) if t: pass #print #print "=========", t.group(1), "==========" for i in mnode.finditer(data): m = i.group(1).strip() if m and is_chinese.search(m): pass #print "".join(map(lambda x: x.strip(), breakline.split(m)))#, i.start()def dirs(dirpath): for current, ds, fs in os.walk(dirpath): for f in fs: if f[-3:] not in ('tml', '.py'): continue main(os.path.join(current, f)) break上面的代码主要实现了对于一个目录下的.py文件以及.html文件中字符常量的遍历(已经将打印字符注释了)。
这主要是解决一个项目中的文案重构工作。
Line ProfilerLineProfiler如其名,能够对每一行的耗时做profile,下面是他的示例代码:
from line_profiler import LineProfilerdef line_profiler(): p = LineProfiler() dirs_lineprofiler = p(dirs) dirs_lineprofiler(".") p.print_stats()这里需要注意的是,调用的函数需要是经过LineProfiler包装后的函数。
dreampuf@HX:~/hub/fortest$ python test_timeit.py Timer unit: 1e-06 sFile: test_timeit.pyFunction: dirs at line 101Total time: 0.108716 sLine # Hits Time Per Hit % Time Line Contents============================================================== 101 def dirs(dirpath): 102 1 1129 1129.0 1.0 for current, ds, fs in os.walk(dirpath): 103 49 56 1.1 0.1 for f in fs: 104 48 50 1.0 0.0 if f[-3:] not in ('tml', '.py'): 105 26 16 0.6 0.0 continue 106 22 107461 4884.6 98.8 main(os.path.join(current, f)) 107 1 4 4.0 0.0 break可以看到,上面的Profile结果没有包含子函数的调用,不过这点可以通过line_profiler.LineProfiler()函数解决,最简单的方法则是以装饰器的形式,标记在想要Profile的函数上。
LineProfiler的特点:
- 直观
- 准确(Python C API 实现的trace)
Memory ProfilerMemory Profiler是基于Line Profiler的一个对于内存占用的代码行Profile类库。使用结果如下:
dreampuf@HX:~/hub/fortest$ python test_timeit.py Filename: test_timeit.pyLine # Mem usage Increment Line Contents================================================ 101 8.68 MB 0.00 MB def dirs(dirpath): 102 8.68 MB 0.01 MB for current, ds, fs in os.walk(dirpath): 103 11.59 MB 2.91 MB for f in fs: 104 11.59 MB 0.00 MB if f[-3:] not in ('tml', '.py'): 105 11.59 MB 0.00 MB continue 106 11.59 MB 0.00 MB main(os.path.join(current, f)) 107 11.59 MB 0.00 MB breakObjgraph在sys.getsizeof的例子中,我们看到了一些不是很直观结论。如果能够通过图示的方法展示内存中对象的引用,那么我们对程序的Profile理解,可能会更容易,Objgraph就是这样的工具。
仍然是sys.getsizeof中的例子:
import objgraphdef objgraph_profile(): a = range(1000000) b = xrange(1000000) c = (i for i in xrange(1000000)) class Entity(object): def __init__(self, name, age): self.name = name self.age = age es = [Entity("dreampuf", 100) for i in xrange(1000)] objgraph.show_refs([es], filename='simple-es.png')我们来分别看看这些容器对象在内存中的形态:
a = range(1000000)b = xrange(1000000) # 直接返回的是一个xrange对象,并没有展开c = (i for i in xrange(1000000)) # 注意,这里不是列表推倒了,而是一个[生成器](http://wiki.python.org/moin/Generators)es = [Entity("dreampuf", 100) for i in xrange(1000)] # 实体对象中,对于基本类型,Python并不会创建多份,而是不断的复用。objgraph的特点:
- 图形结果,直观
- 可以指定遍历层级(第三方模块,Python Module,CPython)
RunSnakeRunRunSnakeRun是一个基于wxPython构建的图形界面stats分析工具。他能够通过profile模块得到的stats结果(以文件的形式),以TreeMap的形式展现出来程序的运行状态。你可以直观的了解到程序执行过程,CPU耗时,调用函数相关的实现,函数调用关系(当然,这些都是stats文件所包含的,只不过RunSnakeRun通过界面更直观的展示了出来)。
Conclusion (From Python Essential Reference,4th)- 理解你的程序
- 不要过度抽象
- 使用合适的Entity
- 使用__slots__
- 避免重复引用
- 不常见情况使用异常
- 避免对常见情况使用异常
- 推崇函数式编程和迭
- 使用装饰器和元类
Guido van Rossum's talkSource
- Avoid overengineering datastructures. Tuples are better than objects (try namedtuple too though). Prefer simple fields over getter/setter functions.
- Built-in datatypes are your friends. Use more numbers, strings, tuples, lists, sets, dicts. Also check out the collections library, esp. deque.
- Be suspicious of function/method calls; creating a stack frame is expensive.
- Don't write Java (or C++, or Javascript, ...) in Python.
- Are you sure it's too slow? Profile before optimizing!
The universal speed-up is rewriting small bits of code in C. Do this only when all else fails.
(Michael Foord) Understand the performance characteristics of basic operations on the builtin types. For example checking for membership in a list is O(N) but for a set or dictionary it is O(1). Adding lists and tuples creates new objects, inserting to the left of a list is O(N) whilst append is O(1) (use a deque instead). And so on.
Reference- Benchmarker
- Benchmarker - a good friend for performance
- Python Speed
- Python Speed - Performance Tips
- 究竟是什么使Linux滴答地响
- The Python Profilers
- Profiling threads
- High Performance Python tutorial
- RunSnakeRun
- 用profile协助程序性能优化
- profile,cProfile, and stats - Performance analysis of Python programs
- Line Profiler
- Memory Profiler
- Objgraph
- A guide to analyzing Python performance
- 性能分析
- Introduction to Python Profiling