Python程序性能分析
- pyflame (github)
- gprof2dot profile data to graph
- flamegraphs 火焰图对性能瓶颈发现非常直观
profiler 分为两类:a. Deterministic(确定性) b. Sampling(采样性).
Deterministic profiler 有比如profile/cProfile模块,原理是使用sys.settrace在函数调用的各个地方打点。好处是剖分信息非常准确,但是缺点也很多:
- High Overhead. 高开销,并且对那些执行很快但是次数很频繁的代码,统计并不准确。 Many engineers don’t use profiling information because they can’t trust its accuracy.
- Lack of Full Stack Information. ??应该不至于吧??
- Lack of Services Written for Profiling. 所有需要剖分的代码都需要explicitly instrumented. 所以如果想分析一段代码,需要修改代码重新上线部署。
Sampling profiler 原理是调用 POSIX Interval timer 每隔一段时间中端进程获取整个堆栈信息,以此来估计每个函数的执行时间和开销。好处是可以修改interval timer来对采样精度以及开销做tradeoff, 缺点是需要一个好的/高效的实现。pyflame / perf 工具就属于这类。
前段时间我们后端的爬虫也出现CPU开销比较大的问题。爬虫是后台长期运行的任务,所以想动态地启动/关闭profiler(所以在一定程度上也解决了explicitly instrumented这个问题)。可以通过启动一个进程定时地从一个地方(比如Redis)里面读取数据来打开和关闭Profiler
global_profiler = cProfile.Profile()
global_profiler.running = False
def dyn_check_profiler(interval=30):
host = conf.HOSTNAME
pid = base_util.get_current_pid()
output_file = os.path.join(conf.DEFAULT_LOGGING_FILE_PATH, '%s.%d.profile.data' % (host, pid))
while True:
tms = TinyMemoryStorage('task.%s.%s.profiler' % (host, pid))
data = tms.get()
if data:
if not global_profiler.running:
logger.debug('profiler enabled')
global_profiler.enable()
global_profiler.running = True
else:
if global_profiler.running:
global_profiler.disable()
global_profiler.running = False
logger.debug('profiler disabled. dump to %s' % output_file)
global_profiler.dump_stats(output_file)
time.sleep(interval)
我对爬虫观察了一段时间,然后使用 gprof2dot 这个工具对profile data图形化展现了出来。可以看到有很大部分时间花费在cookie整合上,不过其实我们并不需要cookie这个功能。
- L1 文件/行号/函数
- L2 累计花费时间(cumtime)
- L3 自身花费时间(tottime)
- L4 调用次数(calls)
可以通过设置cookie policy来关闭cookie. 上线运行一段时间之后可以发现这个爬虫cpu不像原来那么高了。
class BlockAllCookiePolocy(cookielib.CookiePolicy):
return_ok = set_ok = domain_return_ok = path_return_ok = lambda self, *args, **kwargs: False
netscape = True
rfc2965 = hide_cookie2 = False
...
def disable_cookie(self):
self.proxy_ss.cookies.set_policy(BlockAllCookiePolocy())
self.my_ss.cookies.set_policy(BlockAllCookiePolocy())
...