python的性能分析器
python的两个性能分析器: cProfile(pypy没有)和line_profiler
.prof是 cProfile分析的结果
.lprof是 line_profiler分析的结果
cProfile
基于事件的性能分析器
是一种确定性的性能分析器,提供了一组API帮助开发者手机Python程序运行的信息,更准确的说,
是统计每个函数消耗的CPU时间并不统计内存消耗和其他与内存相关的信息。同时它还提供了其他细节,比如函数被调用的次数。
185 function calls (180 primitive calls) in 0.000 seconds Ordered by: standard name ncalls tottime percall cumtime percall filename:lineno(function) ncalls : 调用次数/递归调用flag tottime :函数内部消耗时间(不包括调用其他函数的时间) percall :tottime/ncalls,每次调用平均消耗时间 cumtime :所有子函数消耗的时间累积和(包括递归调用) percall :cumtime/ncalls
Profile类
cProfile.run('re.compile("foo|bar")','status',"cumtime") cProfile.run('re.compile("foo|bar")') Profile(custom_timer=None, time_unit=None, subcalls=True, builtins=True) 支持的API: enable() 开始收集性能分析数据 disable() 停止收集性能分析数据 create_stats() 停止收集数据,并未已收集的数据创建stats对象 print_stats(sort=-1) 创建一个stats对象,打印分析结果 dump_stats(filename) 把当前性能分析的内容写进一个二进制文件(stats文件) run(cmd) runctx(self, cmd, globals, locals): runcall(self, func, *args, **kw) 收及被调用函数func的性能分析信息
import cProfile prof= cProfile.Profile() prof.enable() def func(): pass func() prof.create_stats() prof.print_stats() 通过这种放式对已经写好的代码或者已经测试通过的代码进行性能分析时,可以直接增加或删除性能分析代码,不需要调整代码 命令行模式:python -m cProfile your_script.py -o your_script.profile # 这样做会分析全部代码的性能
Stats类
import pstats # 可以读取和操作stats文件 p = pstats.Stats('stats_file') p.strip_dirs().sort_stats(-1).print_stats() # stats类的构造器可以接受cProfile.Profile 类型的参数,可以不用文件名称做数据源 dump_stats(self, filename) # 把数据保存到一个文件 strip_dirs(self) # 删除报告中所有函数文件名的路径信息。会改变stats实例内部的顺序,任何运行该方法的实例都将随机排列项目 (每一行信息)的顺序。如果两个项目是相同的(函数名一样,文件名一样,行数一样),则会合并。 add(self, *arg_list) sort_stats(self, *field) # 通过一系列条件依次对所有项目进行排序,从而调整stats对象的。 # sort_stats('name','file')首先会把所有项目按照函数名排序,然后对名称相同的项目在按照文件名排序 # 当遇到缩略词有歧义时就会自动按照拟定的会泽进行理解,所以使用的时候要当心,支持的字段 page:28 nfl与stdname的差异: stdname按照字符串打印方式排序,(4,20,30排序后就是20,30,4) nfl是把行号字段作为数字进行排序 一些字段可以用数字代替(-1、0、1、2)(stdname、calls、time、cumulaive) reverse_order() # 反序输出 print_stats(*amount) # 整数:限制打印的行数 # 0.0到1.0(包含)之间的小数:表示按总函数的百分比打印 # 字符串:正则表达式,用来匹配stdname print_callers(*amount) # 输入参数和print_stats一样,但是会显示执行过程中调用的每隔函数的次数,总时间以及累计时间,文件名、 行号和函数名的组合 print_callees(*amount) # 打印一系列调用其他函数的函数。数据显示格式和限制参数与上一个函数相同。
斐波那契的优化方式
import profile import cProfile profiler = cProfile.Profile() profiler.enable() def fib(n): a, b = 0, 1 for i in range(0, n): a,b = b, a+b return a def fib_seq(n): seq = [ ] for i in range(0, n + 1): seq.append(fib(i)) return seq[-1] import pstats print(fib_seq(200)) profiler.create_stats() stats = pstats.Stats(profiler) stats.strip_dirs().sort_stats('cumulative').print_stats() stats.print_callers()
import profile class cached: def __init__(self, fn): self.fn = fn self.cache = {} def __call__(self, *args): try: return self.cache[args] except KeyError: self.cache[args] = self.fn(*args) return self.cache[args] @cached def fib(n): if n <= 1: return n else: return fib(n-1) + fib(n-2) def fib_seq(n): seq = [ ] if n > 0: seq.extend(fib_seq(n-1)) seq.append(fib(n)) return seq profile.run('fib_seq(200)')
import profile import cProfile import pstats profiler = cProfile.Profile() profiler.enable() class cached: def __init__(self, fn): self.fn = fn self.cache = {} def __call__(self, *args): try: return self.cache[args] except KeyError: self.cache[args] = self.fn(*args) return self.cache[args] @cached def fib(n): a, b = 0, 1 for i in range(0, n): a,b = b, a+b return a def fib_seq(n): seq = [ ] for i in range(0, n + 1): seq.append(fib(i)) return seq fib_seq(200) profiler.create_stats() stats = pstats.Stats(profiler) stats.strip_dirs().sort_stats('cumulative').print_stats() stats.print_callers()
越往下效率越高
line_profiler
pip install line_profiler
确定性性能分析器:可以帮助你一行一行地分析函数性能
line_profiler有两种可以获得函数的性能分析数据:用构造器或者用add_function方法。
line_profiler和cProfile.Profile一样,也提供了run,runctx,runcall,enable和disable方法。
但是最后两个函数在嵌入模块统计性能时并不安全,使用时要当心。进行性能分析之后,可以用dump_stats(filename)方法把stats加载到文件中。
也可以用print_stats([stream])方法打印结果,它会把结果打印到sys.stdout里,或者任何其他设置成参数的数据流中。
line_profiler建议使用kernprof工具
利用line_profiler的API进行性能分析
import line_profiler import sys def test(): for i in range(0, 10): print(i**2) print ("End of the function") prof = line_profiler.LineProfiler(test) #pass in the function to profile prof.enable() #start profiling test() prof.disable() #stop profiling prof.print_stats(sys.stdout) #print out the results
C:\Python35\python.exe "H:/script.py" End of the function Timer unit: 2.84432e-07 s Total time: 2.73055e-05 s File: H:/script.py Function: test at line 4 Line # Hits Time Per Hit % Time Line Contents ============================================================== 4 def test(): 5 11 17.0 1.5 17.7 for i in range(0, 10): 6 10 19.0 1.9 19.8 i**2 7 1 60.0 60.0 62.5 print ("End of the function") Time的单位为Time unit
kernprof工具
kernprof工具和line_profiler是集成在一起的,允许我们从源代码中抽象大多数性能分析代码
1、它将和cProfile、lsprof、设置profile模块一起工作 2、会自动寻找脚本文件,如果文件不在当前文件夹,会检测PATH路径 3、将实例化分析器,并把名字添加到__builtins__命名空间的profile中。这样我们就可以在代码中使用性能分析器了。
我们甚至可以直接把它当做装饰器使用,不需要导入。 [root@localhost]$ kernprof -l script.py # 命令行模式,默认会将分析结果写入script.py.lprof [root@localhost]$ kernprof -l -v script.py # 展示在命令行里 @profile def test(): for i in range(0, 10): print(i**2) print ("End of the function") 4、stats性能分析文件可以用pastas.Stats类进行查看,或使用下面的代码查看 [root@localhost]$ python -m pstats script.py.lprof [root@localhost]$ python -m line_profiler script.py.lprof kernprof注意事项: 在性能分析函数调用另一个函数时,没有把每一行消耗的时间增加到总时间上 分析报告中,列表总和表达式的Hit比他们实际消耗的要多很多。
优化实例1
from functools import reduce
def getOffsetUpToWord_1(words, index): if (index == 0): return 0 subList = words[0:index] length = 0 for w in subList: length += len(w) return length + index + 1
def getOffsetUpToWord_2(words, index): """代码简化只是把多余的变量和查询取消了""" if (index == 0): return 0 length = reduce(lambda curr, w: len(w) + curr, words[0:index], 0) return length + index + 1
def addWordLength(curr, w): return len(w) + curr def getOffsetUpToWord_3(words, index): """避免动态地创建函数""" if (index == 0): return 0 length = reduce(addWordLength, words[0:index], 0) return length + index + 1
def getOffsetUpToWord_4(words, index): """去掉if,因为初始值为0""" length = reduce(addWordLength, words[0:index], 0) return length + index + 1
优化实例2
dict={.....} ######## try: dict['xxx'].append('xxxx') except: dict['xxx'] = ['xxxx'] ######## if 'xxx' not in dict: # 大量的检测会消耗时间 dict['xxx'] = ['xxxx'] # 我们可以用defaultdict类,它是dict的子类,只增加了一个功能,如果间不存在,
就是用预先设置的默认值。 dict['xxx'].append('xxxx') ######## from collections import defaultdict def find(*args): return [] dict=defaultdict(find) dict['a'] = 1 print(dict['x']) # [] print(dict['a']) # 1 ######## 变量的声明 line_offset = xxx(asdsa) index = line_offset + currentOffset currentOffset = index 可以替换为: currentOffset +=xxx(asdsa) ######## 被调用的函数内容很少,可以直接写到调用函数内, 但如果被调用的函数,需被多次调用。这么做不方便维护代码。 ※※※※※※※※ 代码的可维护性非常重要 ######## 字符串拼接 + 与 % 相比,后者更费时间

浙公网安备 33010602011771号