성능 분석 및 튜닝 도구 소개
프로그램 실행의 효율성을 높이고 싶고, 어떤 부분이 너무 오래 걸려 병목 현상이 발생하는지, 알고 싶을 때가 항상 있을 것입니다. 프로그램이 실행 중일 때 메모리 및 CPU 사용량. 이때 성능 분석 및 프로그램 조정을 수행하려면 몇 가지 방법이 필요합니다.
컨텍스트 관리자에 의해
컨텍스트 관리자 자체에 의해 타이머를 구현할 수 있습니다. 이전 timeit 소개 기사에서 수행한 작업을 확인하고 __enter__ 및 __exit__ 메서드를 정의하여 관리 기능을 구현할 수 있습니다. 타이밍, 유사:
# timer.py import time class Timer(object): def __init__(self, verbose=False): self.verbose = verbose def __enter__(self): self.start = time.time() return self def __exit__(self, *args): self.end = time.time() self.secs = self.end - self.start self.msecs = self.secs * 1000 # 毫秒 if self.verbose: print 'elapsed time: %f ms' % self.msecs
다음과 같이 사용:
from timer import Timer with Timer() as t: foo() print "=> foo() spends %s s" % t.secs
By Decorator
그러나 내 생각에는 데코레이터 방법이 더 우아합니다
import time from functools import wraps def timer(function): @wraps(function) def function_timer(*args, **kwargs): t0 = time.time() result = function(*args, **kwargs) t1 = time.time() print ("Total time running %s: %s seconds" % (function.func_name, str(t1-t0)) ) return result return function_timer
사용 방법은 매우 간단합니다.
@timer def my_sum(n): return sum([i for i in range(n)]) if __name__ == "__main__": my_sum(10000000)
실행 결과:
➜ python profile.py Total time running my_sum: 0.817697048187 seconds
시스템 자체 시간 명령
사용 예는 다음과 같습니다.
➜ time python profile.py Total time running my_sum: 0.854454040527 seconds python profile.py 0.79s user 0.18s system 98% cpu 0.977 total
위 결과를 보면 스크립트 실행에 CPU 시간이 0.79초, 커널 기능 실행에 0.18초가 소요되어 총 0.977초가 소요됩니다.
그 중 총 시간 - (사용자 시간 + 시스템 시간) = 기타 작업의 입출력 및 시스템 실행에 소요된 시간
python timeit 모듈
을 벤치마킹에 사용할 수 있으며, 예 프로그램이 실행될 수 있는 블록 수를 확인하기 위해 프로그램이 실행되는 횟수를 편리하게 반복합니다. 자세한 내용은 이전에 작성한 글을 참고하시기 바랍니다.
cProfile
주석이 달린 사용 예를 살펴보세요.
#coding=utf8 def sum_num(max_num): total = 0 for i in range(max_num): total += i return total def test(): total = 0 for i in range(40000): total += i t1 = sum_num(100000) t2 = sum_num(200000) t3 = sum_num(300000) t4 = sum_num(400000) t5 = sum_num(500000) test2() return total def test2(): total = 0 for i in range(40000): total += i t6 = sum_num(600000) t7 = sum_num(700000) return total if __name__ == "__main__": import cProfile # # 直接把分析结果打印到控制台 # cProfile.run("test()") # # 把分析结果保存到文件中 # cProfile.run("test()", filename="result.out") # 增加排序方式 cProfile.run("test()", filename="result.out", sort="cumulative")
cProfile은 분석 결과를 result.out 파일에 저장하는데, 바이너리 형태로 저장되어 있으니 직접 보시려면 제공된 pstats를 이용해서 보시면 됩니다.
import pstats # 创建Stats对象 p = pstats.Stats("result.out") # strip_dirs(): 去掉无关的路径信息 # sort_stats(): 排序,支持的方式和上述的一致 # print_stats(): 打印分析结果,可以指定打印前几行 # 和直接运行cProfile.run("test()")的结果是一样的 p.strip_dirs().sort_stats(-1).print_stats() # 按照函数名排序,只打印前3行函数的信息, 参数还可为小数,表示前百分之几的函数信息 p.strip_dirs().sort_stats("name").print_stats(3) # 按照运行时间和函数名进行排序 p.strip_dirs().sort_stats("cumulative", "name").print_stats(0.5) # 如果想知道有哪些函数调用了sum_num p.print_callers(0.5, "sum_num") # 查看test()函数中调用了哪些函数 p.print_callees("test")
test()가 어떤 함수를 호출하는지 확인하기 위해 출력 예제를 가로채세요.
➜ python python profile.py Random listing order was used List reduced from 6 to 2 due to restriction <'test'> Function called... ncalls tottime cumtime profile.py:24(test2) -> 2 0.061 0.077 profile.py:3(sum_num) 1 0.000 0.000 {range} profile.py:10(test) -> 5 0.073 0.094 profile.py:3(sum_num) 1 0.002 0.079 profile.py:24(test2) 1 0.001 0.001 {range}
profile.Profile
cProfile은 사용자 정의할 수 있는 클래스도 제공합니다. , 좀 더 자세히 분석할 수 있습니다. 자세한 내용은 설명서를 참조하세요.
형식은 다음과 같습니다: class profile.Profile(timer=None, timeunit=0.0, subcalls=True, 내장=True)
다음 예는 공식 문서에서 가져온 것입니다.
import cProfile, pstats, StringIO pr = cProfile.Profile() pr.enable() # ... do something ... pr.disable() s = StringIO.StringIO() sortby = 'cumulative' ps = pstats.Stats(pr, stream=s).sort_stats(sortby) ps.print_stats() print s.getvalue()
lineprofiler
lineprofiler是一个对函数进行逐行性能分析的工具,可以参见github项目说明,地址: https://github.com/rkern/line...
示例
#coding=utf8 def sum_num(max_num): total = 0 for i in range(max_num): total += i return total @profile # 添加@profile 来标注分析哪个函数 def test(): total = 0 for i in range(40000): total += i t1 = sum_num(10000000) t2 = sum_num(200000) t3 = sum_num(300000) t4 = sum_num(400000) t5 = sum_num(500000) test2() return total def test2(): total = 0 for i in range(40000): total += i t6 = sum_num(600000) t7 = sum_num(700000) return total test()
通过 kernprof 命令来注入分析,运行结果如下:
➜ kernprof -l -v profile.py Wrote profile results to profile.py.lprof Timer unit: 1e-06 s Total time: 3.80125 s File: profile.py Function: test at line 10 Line # Hits Time Per Hit % Time Line Contents ============================================================== 10 @profile 11 def test(): 12 1 5 5.0 0.0 total = 0 13 40001 19511 0.5 0.5 for i in range(40000): 14 40000 19066 0.5 0.5 total += i 15 16 1 2974373 2974373.0 78.2 t1 = sum_num(10000000) 17 1 58702 58702.0 1.5 t2 = sum_num(200000) 18 1 81170 81170.0 2.1 t3 = sum_num(300000) 19 1 114901 114901.0 3.0 t4 = sum_num(400000) 20 1 155261 155261.0 4.1 t5 = sum_num(500000) 21 1 378257 378257.0 10.0 test2() 22 23 1 2 2.0 0.0 return total
hits(执行次数) 和 time(耗时) 值高的地方是有比较大优化空间的地方。
memoryprofiler
类似于"lineprofiler"对基于行分析程序内存使用情况的模块。github 地址:https://github.com/fabianp/me... 。ps:安装 psutil, 会分析的更快。
同样是上面"lineprofiler"中的代码,运行 python -m memory_profiler profile.py 命令生成结果如下:
➜ python -m memory_profiler profile.py Filename: profile.py Line # Mem usage Increment Line Contents ================================================ 10 24.473 MiB 0.000 MiB @profile 11 def test(): 12 24.473 MiB 0.000 MiB total = 0 13 25.719 MiB 1.246 MiB for i in range(40000): 14 25.719 MiB 0.000 MiB total += i 15 16 335.594 MiB 309.875 MiB t1 = sum_num(10000000) 17 337.121 MiB 1.527 MiB t2 = sum_num(200000) 18 339.410 MiB 2.289 MiB t3 = sum_num(300000) 19 342.465 MiB 3.055 MiB t4 = sum_num(400000) 20 346.281 MiB 3.816 MiB t5 = sum_num(500000) 21 356.203 MiB 9.922 MiB test2() 22 23 356.203 MiB 0.000 MiB return total