Python 性能分析工具py-spy与Scalene

 

Py-Spy介绍

引用官方的介绍:

Py-Spy是Python程序的抽样分析器。 它允许您可视化查看Python程序在哪些地方花了更多时间,整个监控方式无需重新启动程序或以任何方式修改工程代码。 Py-Spy的开销非常低:它是用Rust编写的,速度与编译的Python程序不在同一个进程中运行。 这意味着Py-Spy可以安全地用于生成生产环境中的Python应用调优分析。
github:https://github.com/benfred/py-spy

安装

pip install py-spy

安装后使用py-spy - h可以验证安装,并查看使用帮助。

py-spy从命令行工作,并获取要从中采样的程序的PID或要运行的python程序的命令行。py-spy具有三个子命令record, topdump

  • record生成火焰图
  • top实时查看每个函数运行时间并统计
  • dump显示每个python线程的当前调用堆栈

 

 

使用py-spy 生成火焰图

​ py-spy是一个非常好用而且简单的库,看完他的readme 介绍文档基本就可以入手使用spy。这个工具一是可以生成profile 火焰图,二是可以定位到程序中最耗时间的代码的位置。它的优点在于完全不用修改代码,相比较其他的一些性能调查工具,py-spy这一点非常棒,当你debug 一个线上正在运行的程序的时候,只需要提供进程id,py-spy 就可以直接生成火焰图。

py-spy record -o profile.svg --pid 12345

或者

 py-spy record -o profile.svg-python myprogram.py

"12345" 为程序运行的pid,当运行这行命令的时候,py-spy 开始抽样的程序simlple 并且生成火焰图,我们可以等待1分钟左右 ctrl+c 结束,这时候会在运行这行命令的当前目录下生成 profile.svg 火焰图, 如下图:

undefined

 火焰图的分析非常简单直观,主要是看"平顶",看图中最下方那个峰顶是平的,那么程序的性能问题就可以从这里入手去解决,这里不详细介绍火焰图看法,不明白的同学可以自行百度。

​ 通过生成火焰图分析程序瓶颈大概率可以找到并解决80%的程序性能问题,但是还有一种问题,如果我的火焰图没有平顶,但是程序依旧很慢,该如何定位问题?

没有平顶情况下,定位程序中耗时最多函数/代码

如下图,通过火焰图并没有发现程序中的平顶
undefined

 

Top功能

这时候要用到py-spy 提供的 top 命令,Top显示了在python程序中花费最多时间的功能的实时视图,类似于unix top命令。

py-spy top --pid 12345

py-spy top-python myprogram.py

​ 输入上述命令后,在控制台会显示程序实时的运行状态,这里可以介绍一下图中4个参数的含义, 然后可以通过按1,2,3,4 四个按键,让程序按照下图所述排序。

  1按%Own排序(当前在该函数中花费的时间的百分比)
  2按%Total排序(函数及其子级中当前的时间百分比)
  3按OwnTime排序(函数中花费的总时间)
  4按TotalTime排序(该函数及其子项花费的总时间)

​ 比较直观的 使用3 , 可以比较直接的看出程序运行中,所占比消耗时间最多的函数,然后从函数如图进行分析,如下图,可以看出 是wrap 装饰器函数消耗的时间最长,我们用wrapt 这个c写的装饰器进行替换后效率有了明显的提升。

​ undefined

总结 : 使用py-spy 相对于其他一些python性能分析工具,优势在于使用非常简单,而且无须对代码做任何改动,并且可以在保护现场情况下,直接生成火焰图,还可查看实时程序运行状态。

 

火焰图怎么看

首先你需要知道:

X方向是采样时间。

Y方向是函数调用栈。

如果给你一个这样的火焰图,你应该得出什么信息:

1.a()是开始的执行函数,但没有消耗cpu,在这个函数里执行了b(),h()。

2.a()的两个分支b()h(),这表明a()里面可能有一个条件语句,继续可以看到b()分支消耗的 CPU 大大高于h()。

3.h()函数没有消耗cpu,cpu全被i()函数占有。

4.b()函数这条支路继续往上,一直到d(),由d()函数的子函数e()消耗一部分cpu,f()下的g()消耗一部分cpu,你会发现d()的最右边往上缺了一块,这块就是d()执行消耗的cpu。

结论:

消耗cpu的函数为e(),g(),d(),i()。

因此,如果要调查性能问题,首先应该调查g(),其次是i()

 

Scalene

Scalene 是一个 Python 的高性能 CPU内存分析器,它可以做到很多其他Python分析器不能做到的事情。它在能提供更多详细信息的同时,比其他的分析器要快几个数量级。

  1. Scalene 是 很快的。它使用采样的方式而不是直接测量或者依靠Python的追踪工具。它的开销一般不超过10-20% (通常更少)。

  2. Scalene 是 精确的。和大部分其他的Python分析器不同,Scalene 在 行级别 下执行CPU分析,在你的程序中指出对应代码行的执行时间。和大多数分析器所返回的功能级分析结果相比,这种程度的细节可能会更有用。

  3. Scalane 可以区分在Python中运行的时间和在native代码(包括库)中花费的时间。大多数的Python程序员并不会去优化native代码(通常在Python实现中或者所依赖的外部库),所以区分这两种运行时间,有助于开发者能够将优化的工作专注于他们能够实际改善的代码上。

  4. Scalene 可以 分析内存使用情况。除了追踪CPU使用情况,Scalene还指出对应代码行的内存增长。这是通过指定内存分配器来实现的。

  5. NEW! Scalene 会生成 每行 的内存分析,以此更容易的追踪内存泄露。

  6. NEW! Scalene 会分析 内存拷贝量, 从而易于发现意外的内存拷贝。特别是因为跨越Python和底层库的边界导致的意外 (例如:意外的把 numpy 数组转化成了Python数组,反之亦然)。

安装

Scalene 通过 pip 包的形式进行分发,可以运行在Mac OS X和Linux平台(包括在Windows WSL2中运行的Ubuntu)。

你可以通过下面的方式安装:

% pip install scalene

或者

% python -m pip install scalene

注意: 现在这样安装Scalene,是不会安装内存分析的库,所以你只能用它来执行CPU的分析。如果要使用它的内存分析能力,你需要下载这个代码仓库。

NEW: 你现在可以通过以下命令,在 Mac OS X 上使用 brew 安装内存分析的部分:

% brew tap emeryberger/scalene
% brew install --head libscalene

这将会安装一个你可以使用的 scalene 脚本(下面会提到)。

使用

下面的命令会让 Scalene 在提供的示例程序上执行 行级别的CPU分析。

% python -m scalene test/testme.py

如果你使用Homebrew安装 Scalene 库,你只需要执行 scalene 就可以执行行级别的CPU和内存分析:

% scalene test/testme.py

否则,你需要运行 make 来先构建一个指定的内存分配器:

% make

在 Mac OS X 系统上进行分析(不使用Homebrew安装):

% DYLD_INSERT_LIBRARIES=$PWD/libscalene.dylib PYTHONMALLOC=malloc python -m scalene test/testme.py

在Linux系统上分析:

% LD_PRELOAD=$PWD/libscalene.so PYTHONMALLOC=malloc python -m scalene test/testme.py

执行时增加 --help 来查看全部配置:

% python3 -m scalene --help
usage: scalene [-h] [-o OUTFILE] [--profile-interval PROFILE_INTERVAL]
[--wallclock]
prog

Scalene: a high-precision CPU and memory profiler.
https://github.com/emeryberger/Scalene

for CPU profiling only:
% python -m scalene yourprogram.py
for CPU and memory profiling (Mac OS X):
% DYLD_INSERT_LIBRARIES=$PWD/libscalene.dylib PYTHONMALLOC=malloc python -m scalene yourprogram.py
for CPU and memory profiling (Linux):
% LD_PRELOAD=$PWD/libscalene.so PYTHONMALLOC=malloc python -m scalene yourprogram.py

positional arguments:
prog program to be profiled

optional arguments:
-h, --help show this help message and exit
-o OUTFILE, --outfile OUTFILE
file to hold profiler output (default: stdout)
--profile-interval PROFILE_INTERVAL
output profiles every so many seconds.
--wallclock use wall clock time (default: virtual time)

对比其他分析器

性能和功能

下面的表格把 scalene 和不同分析器的性能做了比较。运行的示例程序 (benchmarks/julia1_nopil.py) 来自于 Gorelick 和 Ozsvald 的 《高性能Python编程》。所有的这些结果都是在 2016款 MacBook Pro上运行的。

这个表格是其他分析器 vs. Scalene 的功能比较。

输出

Scalene 打印被分析程序中带注释的源代码,以及程序在同目录和子目录使用到的任何模块。下面是一个来自 pystone.py pystone.py 的片段,只使用了CPU分析:

benchmarks/pystone.py: % of CPU time = 100.00% out of   3.66s.
| CPU % | CPU % |
Line | (Python) | (native) | [benchmarks/pystone.py]
--------------------------------------------------------------------------------
[... lines omitted ...]
137 | 0.27% | 0.14% | def Proc1(PtrParIn):
138 | 1.37% | 0.11% | PtrParIn.PtrComp = NextRecord = PtrGlb.copy()
139 | 0.27% | 0.22% | PtrParIn.IntComp = 5
140 | 1.37% | 0.77% | NextRecord.IntComp = PtrParIn.IntComp
141 | 2.47% | 0.93% | NextRecord.PtrComp = PtrParIn.PtrComp
142 | 1.92% | 0.78% | NextRecord.PtrComp = Proc3(NextRecord.PtrComp)
143 | 0.27% | 0.17% | if NextRecord.Discr == Ident1:
144 | 0.82% | 0.30% | NextRecord.IntComp = 6
145 | 2.19% | 0.79% | NextRecord.EnumComp = Proc6(PtrParIn.EnumComp)
146 | 1.10% | 0.39% | NextRecord.PtrComp = PtrGlb.PtrComp
147 | 0.82% | 0.06% | NextRecord.IntComp = Proc7(NextRecord.IntComp, 10)
148 | | | else:
149 | | | PtrParIn = NextRecord.copy()
150 | 0.82% | 0.32% | NextRecord.PtrComp = None
151 | | | return PtrParIn

 

refer:

https://yq.aliyun.com/articles/741628

http://www.python88.com/topic/62116

posted @ 2020-07-24 20:03  -零  阅读(2866)  评论(0编辑  收藏  举报