暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

python 内存系列(7)-memory_profiler逐行分析每行代码内存占用情况

小儿来一壶枸杞酒泡茶 2021-03-08
6946

前言

之前有介绍关于性能分析上相关几个工具主要有:cProfile和line_profiler,这两个主要是针对耗时统计分析的,如果需要分析每一行代码所增减的内存占用情况的话,就需要祭出‘ memory_profiler’。memory_profiler 是一个第三方库的模块,它可以有效定位到我们没一行代码占用内存的情况。

环境:python 3.5

本节主要内容:

  • 相关单位换算介绍
  • memory_profiler 安装
  • memory_profiler 简单例子的使用
  • memory_profiler 把分析结果写入文件
  • memory_profiler 可视化分析结果
  • memory_profiler 多进程情况下的分析
  • memory_profiler 结合Flask框架
  • memory_profiler设置内存断点

铺垫篇

1、存单位换算说明:

一、  b与B(位与字节)

  • bit 位,计算机中最小的数据单位。每一位的状态只能是0或1 。即 1b=1bit
  • byte 字节,8个二进制位构成1个字节。由0或1合计8位任意搭配组成,每一位的状态只能是0或1

    1 byte = 8 bit

    1个英文字母或者数字占用1个byte的空间,1个汉字占据2个byte的空间。

二、K与Ki(千与千位二进制)

  • kb (kilobyte)千字节.

    k(kilo) 是千的意思,比如1千米,1km;1千克.1kg.等等.加上后面的单位数据乘以1000即可.如工资3k.

    1k=1kilo=

    1kb=1000byte=8bit*1000(8千位)

  • Ki(Kibi,Kilo binary的缩写):千位二进制的意思。为了表示信息数据传输二进制而创设的1024,一般用Ki而不用ki.

    Ki=1Kibi= = 1024

  • 1KiB(Kibibyte)=1024B(byte) :KiB(Kibibyte)是千位二进制字节的意思

  • 1Kib=1024*8b(bit) :Kib(Kibibit)就是千位二进制位的意思.

  • 1Ki=1ki :一般都用Ki.1KiB=8Kib

三、M与Mi(兆与兆位二进制)

  • 1M=1mega= :M(mega)就是兆的意思.这里和米(m)不是一个单位.

  • 1MB=B :MB(megabyte)就是兆字节的意思.

    1MB=B = (byte)=*8(bit)

  • 1Mb=106*8b : Mb(megabit)就是兆位的意思.

    1Mb=*8(bit)

  • 1m=1M :规定用M.1MB=8Mb.电信说的100兆宽带就是100Mb,因此要转换为我们常说的网速单位需要除以8,即100Mb=100/8=12.5MB。

  • 1Mi=1Mibi= :Mi(mebi,即Mega binary的缩写)就是兆位二进制的意思.

  • 1MiB=B :MiB(mebibyte)就是兆位二进制字节的意思.

  • 1Mib=220b :Mib(mebibit)就是兆位二进制位的意思.

  • 1mi=1Mi :规定用Mi.1MiB=8Mib.

四、G与Gi(吉与吉位二进制)

  • 1G=1giga=109 G(giga) :就是吉的意思.这里和克(g)不是一个单位.
  • 1GB=109B GB(gigabyte): 就是吉字节的意思.
  • 1Gb=109*8b Gb(gigabit): 就是吉位的意思.
  • 1g=1G :规定用G. 1GB=8Gb.

    1 千兆字节(g)=1024 兆字节(MB)

  • 1Gi=1gibi=109 : Gi(gibi,Giga binary的缩写)就是吉位二进制的意思.
  • 1GB=109B :GiB(gibibyte)就是吉位二进制字节的意思.
  • 1Gb=109*8b :Gib(gibibit)就是吉位二进制位的意思.
  • 1gi=1Gi :规定用Gi. 1GiB=8Gib.

2、其他单位说明

1、 MiB、KiB、Mb、kb

  • 1kb=1024B 或者1KB=1000B

  • 1KiB=1024B

  • 1Mb=1024KB

  • 1MiB=1024KB

2、Bps、bps:

  • Bps Byte per second,字节/秒.
  • bps bit per second 位/秒

    如家用的2M宽带,也就是2M bps,换成Bps也就是(2M/8 )Bps = 256K Bps,所以我们用2M宽带下载时的最大下载速度就是256KB左右。我们平常所说的百兆、千兆网卡,其单位就是bps,而非Bps。

3、GT/s、GB/s:

常用来描述计算机内部总线的传输速度 GT/s:giga transfers per second,可以通俗的理解为“G次/秒”,表示传输速度。如Core i7 QPI总线传输速度可以达到4.8GT/s,HT 3.0总线可以达到5.2GT/s,也就是 说QPI总线每秒可以传输4.8G次,HT 3.0总线则是每秒5.2G次。

4、GB/s

即G Byte/s,如QPI总线的带宽是25.6GB/s,即每秒能传输25.6G字节。两者间的换算:比如1333MHz的FSB,每秒传输1333M(1.333G)次,每次传输64 bit数据,也就是8 Byte,所以它的传输带宽就是1.333*8=10.7GB/S。

实践篇

官网地址

https://pypi.org/project/memory-profiler/

安装

(.venv) PS D:\code\vscode\py>  pip install memory_profiler

PS:安装psutil可加速memory_profile(windos环境下建议安装psutil)

pip install psutil



1.最简示例

Filename: d:/code/vscode/py/memory_profiler_oy.py

Line #    Mem usage    Increment  Occurences   Line Contents
============================================================
   13     30.5 MiB     30.5 MiB           1   @profile
   14                                         def ceshi():
   15     30.5 MiB      0.0 MiB           1       a = [1, 1, 2, 2, "小钟同学"]
   16     30.5 MiB      0.0 MiB           1       return a


(.venv) PS D:\code\vscode\py> 


参数说明:

  • Mem usage:表示执行该行后Python解释器的内存使用情况
  • Increment:表示当前行的内存相对于上一行的差异,即自己本身增长了多少,如果减少了则不显示.

关于MIB:

1MB=B = (byte)=*8(bit)

1MiB = 1,024KiB(千字节(kb)) =Byte=1048576 Byte=1.1757813兆字节(mb)

1 千字节kb=0.0009766 兆字节mb 1MiB = 1,024KiB=1,024kb=1024*0.0009766 兆字节mb= 1.0000384MB

分析占用情况:说明30.5MiB=30.5 *0.0009766兆字节(mb)=0.0297863M

2.官网示例

Filename: d:/code/vscode/py/memory_profiler_oy.py

Line #    Mem usage    Increment  Occurences   Line Contents
============================================================
   13     30.4 MiB     30.4 MiB           1   @profile
   14                                         def my_func():
   15     34.3 MiB      3.8 MiB           1       a = [1] * (10 ** 6)
   16    110.6 MiB     76.3 MiB           1       b = [2] * (2 * 10 ** 7)
   17     34.3 MiB    -76.3 MiB           1       del b
   18     34.3 MiB      0.0 MiB           1       return a


(.venv) PS D:\code\vscode\py>

参数说明:

  • line  已分析的代码的行号
  • Mem usage:表示执行该行后Python解释器的内存使用情况
  • Increment:表示当前行的内存相对于上一行的差异,即自己本身增长了多少,如果减少了则不显示.

3.分析结果写入本地

from memory_profiler import profile


@profile(precision=4, stream=open('memory_profiler.log''w+'))
def my_func():
    a = [1] * (10 ** 6)
    b = [2] * (2 * 10 ** 7)
    del b
    sdsdk()
    return a


@profile(precision=4, stream=open('memory_profiler2.log''w+'))
def sdsdk():
    sasd = []
    for i in range(2000):
        sasd.append(1)


if __name__ == '__main__':
    my_func()


最终文件生成:

4.以图片的形式展示出来分析结果

4.1 生产文件信息

mprof run test.py 

错误:

(.venv) PS D:\code\vscode\py> mprof run .\memory_profiler_oy.py
mprof: Sampling memory every 0.1s
running new process
running as a Python program...
Traceback (most recent call last):
  File "C:\Users\mayn\AppData\Local\Programs\Python\Python35-32\lib\runpy.py", line 193, in _run_module_as_main
    "__main__", mod_spec)
  File "C:\Users\mayn\AppData\Local\Programs\Python\Python35-32\lib\runpy.py", line 85, in _run_code
    exec(code, run_globals)
  File "d:\code\vscode\py\.venv\lib\site-packages\memory_profiler.py", line 1303, in <module>
    exec_with_profiler(script_filename, prof, args.backend, script_args)
  File "d:\code\vscode\py\.venv\lib\site-packages\memory_profiler.py", line 1204, in exec_with_profiler
    exec(compile(f.read(), filename, 'exec'), ns, ns)
UnicodeDecodeError: 'gbk' codec can't decode byte 0xad in position 23: illegal multibyte sequence
(.venv) PS D:\code\vscode\py> 

网上解决方案:

> gbk解码错误,解决方案:
- 首先进入 memory_profiler.py文件中,
- 找到第with open关键词, 把with open(filename) as f: 更改成 with open(filename, encoding=’utf-8’) as f:

再执行命令:

 mprof run .\memory_profiler_oy.py

生成两个文件(因为两个装饰器):

4.2 记录了内存随时间的变化的图片可视化生成

(.venv) PS D:\code\vscode\py> mprof plot .\mprofile_20210308140851.dat
matplotlib is needed for plotting.
No module named 'pylab'
(.venv) PS D:\code\vscode\py> 

解决错误:

pip install matplotlib

PS:需要依赖使用matplotlib来进行图片的绘制

然后继续执行生产图片的命令:

 mprof plot .\mprofile_20210308140851.dat

mprof的可用命令是:

  • mprof  run:运行可执行文件,记录内存使用情况(默认情况下仅跟踪父进程)
(.venv) PS D:\code\vscode\py> mprof run .\memory_profiler_oy.py
mprof: Sampling memory every 0.1s
running new process
running as a Python program...

  • mprof  plot:绘制一个记录的内存使用情况(默认情况下,最后一个Using last profile data.)
(.venv) PS D:\code\vscode\py> mprof plot
Using last profile data.


  • mprof  list:以一种用户友好的方式列出所有记录的内存使用情况文件。
(.venv) PS D:\code\vscode\py> mprof  list
0 mprofile_20210308140851.dat 14:08:51 08/03/2021
1 mprofile_20210308141254.dat 14:12:54 08/03/2021
(.venv) PS D:\code\vscode\py>

  • mprof clean:删除所有记录的内存使用情况文件。
  • mprof rm:删除特定记录的内存使用情况文件

5、涉及多进程情况下分析结果

当使用mprof  run:运行可执行文件,记录内存使用情况(默认情况下仅跟踪父进程)。如果我们的程序涉及到多进程的时候,就如果需要分别跟进每个进程的内存使用情况的话,就需要做额外的参数传递处理:目前我们的测试代码为:

from memory_profiler import profile


@profile(precision=4, stream=open('memory_profiler.log''w+'))
def my_func():
   a = [1] * (10 ** 6)
   b = [2] * (2 * 10 ** 7)
   del b
   sdsdk()
   return a


@profile(precision=4, stream=open('memory_profiler2.log''w+'))
def sdsdk():
   sasd = []
   for i in range(2000):
       sasd.append(1)


if __name__ == '__main__':
   my_func()


第一种机制:

把主进程和子进程的内存使用情况的报告都输出,使用include_children参数

执行:

mprof run --include-children xxxxxx.py

结果:

 (.venv) PS D:\code\vscode\py> mprof run --include-children .\memory_profiler_oy.py

会生产三个文件,一个是日志文件,另一个是xxx.dat文件:

第二种机制:

分别的统计跟踪每个子进程 执行:

mprof run --multiprocess xxxxxx.py

6、memory_profiler 结合Flask框架

from memory_profiler import profile


from flask import Flask, jsonify
import time


app = Flask(__name__)


@app.route('/')
@profile(precision=4)
def line_test():
    for item in range(5):
        time.sleep(0.1)

    return jsonify({'code': 200})


if __name__ == '__main__':
    app.run()


输出结果:

Filename: d:/code/vscode/py/memory_profiler_oy.py

Line #    Mem usage    Increment  Occurences   Line Contents
============================================================
    20  35.7969 MiB  35.7969 MiB           1   @app.route('/')
    21                                         @profile(precision=4)
    22                                         def line_test():
    23  35.7969 MiB   0.0000 MiB           6       for item in range(5):
    24  35.7969 MiB   0.0000 MiB           5           time.sleep(0.1)
    25
    26  35.7969 MiB   0.0000 MiB           1       return jsonify({'code': 200})


127.0.0.1 - - [08/Mar/2021 16:06:29] "GET / HTTP/1.1" 200 -

7、mprof plot参数补充说明:

  • mprof plot --flame(函数名称和时间戳名称将显示在悬停上).\mprofile_20210308140851.dat
    • --flame(函数名称和时间戳名称将显示在悬停上)
  • mprof plot -t'记录的内存使用情况
  • mprof plot -n 使用n标志隐藏函数时间戳
  • mprof plot -s 使用s标志绘制趋势线及其数值斜率
    • mprof plot -s >0
    • mprof plot -s ~0
    • mprof plot -s <0

pS:趋势线用于说明目的,并绘制为(非常)小的虚线。

> 0,可能意味着内存泄漏。
〜0(如果为0或接近0),则可以认为内存使用情况稳定。
<0将根据预期的进程内存使用模式来解释,也可能意味着采样周期太小。

8、memory_profiler设置内存断点

意思就是给我们的内存使用量设置一个阀值,当触发到这个阀值的时候,停止执行并运行到pdb调试器。使用此功能的前提是不需要再相关的函数加上@profile装饰功能。

然后使用命令启动程序:

$ python -m memory_profiler --pdb-mmem = 100 my_script.py

其中:--pdb-mmem = 100 中的100表示的就是内存阈值的数字,以MB为单位。当我们的函数消耗内存达到100 MB以上的内存,就进入pdb调试器。

参考资料:

官网参考 :https://pypi.org/project/memory-profiler/

更多示例对比参考 https://www.cnblogs.com/kaituorensheng/p/5669861.html#_label9

说明

部分的图或资料来互联网收集整理,如有侵权,烦请联系,我会立即进行删除。

End

纯属个人实践中相关经验之谈,如有纰漏,还希望大佬们多多提点!小钟同学 | 文  【原创】| QQ:308711822


文章转载自小儿来一壶枸杞酒泡茶,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论