Python 内存和分析工具

发布时间: 更新时间: 总字数:1459 阅读时间:3m 作者: IP上海 分享 网址

Python 内存

介绍

python 中内存分配,参考

    Object-specific allocators
    _____   ______   ______       ________
   [ int ] [ dict ] [ list ] ... [ string ]       Python core         |
+3 | <----- Object-specific memory -----> | <-- Non-object memory --> |
    _______________________________       |                           |
   [   Python's object allocator   ]      |                           |
+2 | ####### Object memory ####### | <------ Internal buffers ------> |
    ______________________________________________________________    |
   [          Python's raw memory allocator (PyMem_ API)          ]   |
+1 | <----- Python memory (under PyMem manager's control) ------> |   |
    __________________________________________________________________
   [    Underlying general-purpose allocator (ex: C library malloc)   ]
 0 | <------ Virtual memory allocated for the python process -------> |

   =========================================================================
    _______________________________________________________________________
   [                OS-specific Virtual Memory Manager (VMM)               ]
-1 | <--- Kernel dynamic storage allocation & management (page-based) ---> |
    __________________________________   __________________________________
   [                                  ] [                                  ]
-2 | <-- Physical memory: ROM/RAM --> | | <-- Secondary storage (swap) --> |

垃圾回收

  • 查看某个对象的引用计数(参考),得到的值会比期望的多 1(该调用的临时引用)
import sys
sys.getrefcount(obj)
  • 当使用如 del 等释放变量时,引用计数若变为 0,当垃圾回收时会清空内存
    • 大对象,要使用 del 删除
  • 垃圾回收时,会STW(stop the world),不进行其他任务,频繁的垃圾回收会大大降低效率
  • Python 运行时,会标记对象分配/取消(object allocation/object deallocation)的次数,当两者的差值高于某阈值时,来回启动垃圾回收
    • 700: 即上面说的阈值
    • 10: 每 10 次 0 代垃圾回收,会配合 1 次 1 代垃圾回收
    • 10: 每 10 次 1 代垃圾回收,才会有 1 次 2 代垃圾回收
>>> import gc
>>> gc.get_threshold()
(700, 10, 10)
  • 手动启动垃圾回收
>>> import gc
>>> gc.collect()
  • 分代回收
    • 0 代:所有新建的对象都是 0 代对象
    • 若某一代对象经历垃圾回收后依然存活,则被归入下一代对象

内存池

  • Python 的大内存和小内存以 256K 为分界
    • 大内存使用 malloc 分配
    • 小内存使用内存池分配
  • 内存池
    • 第3层:对Python对象直接操作
    • 第2层/第1层:每次申请内存不会自动释放,不使用时放入内存池

tcmalloc

  • 为了提高内存分配性能,经常使用 tcmalloc 代替默认的 malloc() 实现,因为 tcmalloc 在分配和取消分配大型对象时,碎片较少 参考
    • 据了解,一些内存密集型程序在使用默认 malloc() 时会泄露堆地址空间(同时释放它们使用的所有单个对象),但在改用 tcmalloc 后表现良好
    • 此外,tcmalloc 还包含堆剖析器,可追踪可能发生泄漏的位置
  • TCMalloc 是 Google 对 C’s malloc() 和 C++’s operator new 的定制实现,用于在我们的 C 和 C++ 代码中进行内存分配。TCMalloc 是一种快速的多线程 malloc 实现
# apt search google-perftools
apt install libgoogle-perftools4 libgoogle-perftools-dev google-perftools
dpkg -L libgoogle-perftools4 | grep so

LD_PRELOAD="/usr/lib/x86_64-linux-gnu/libtcmalloc.so.4" python3 script.py

gdb

使用 gdb 调试 python 程序,通过端点随时查看

$ gdb python <pid>

# 查看线程
(gdb) info threads

# 查看调用栈
(gdb) bt
(gdb) py-bt

# coredump
(gdb) generate-core-file

# 相关扩展命令
(gdb) py<TAB><TAB>

pyrasite 进入正在运行的程序

pip install pyrasite

pyrasite-shell <pid>

objgraph

objgraph 支持查看增长最快的类型,但无法得知占用内存最多的变量,更多参考

安装

pip3 install objgraph

使用

import objgraph

# 查看最常用的类型
objgraph.show_most_common_types(limit=20)

# 查看增长最快的类型
objgraph.show_growth(limit=10)

# 查看某个类型
objgraph.by_type('list')

# 生成一个图
objgraph.show_refs([an_object], filename='sample-graph.png')

memory_profiler

memory_profiler 用来分析每行代码的内存使用情况,使用

  • 通过装饰器 @profile 实现
  • 调用 python -m memory_profiler demo.py
  • 缺点测试时需要调整装饰器

安装

pip3 install memory-profiler

示例

  • demo1.py
@profile
def demo1():
    c = 0
    for item in xrange(100000):
        c += 1
    print(c)

if __name__=='__main__':
    demo1()

# 下面的方法执行时不需要指定 `-m memory_profiler`
# from memory_profiler import profile
# @profile
# def demo1():
#     c = 0
#     for item in xrange(100000):
#         c += 1
#     print(c)

# if __name__=='__main__':
#     demo1()
  • 测试

方法一:

$ python3 -m memory_profiler demo1.py
100000
Filename: demo1.py

Line #    Mem usage    Increment  Occurrences   Line Contents
=============================================================
     1   18.309 MiB   18.309 MiB           1   @profile
     2                                         def demo1():
     3   18.309 MiB    0.000 MiB           1       c = 0
     4   18.309 MiB    0.000 MiB      100001       for item in range(100000):
     5   18.309 MiB    0.000 MiB      100000           c += 1
     6   18.309 MiB    0.000 MiB           1       print(c)
  • 说明
    • Mem usage: 内存占用情况
    • Increment: 执行代码时,内存增加多少
    • 参数 @profile(precision=4, stream=open('memory_profiler.log', 'w+'))
      • precision 精度
      • stream 流输出到文件

方法二:

$ mprof run demo1.py
mprof: Sampling memory every 0.1s
running new process
running as a Python program...
100000

# 结果
$ ls
mprofile_2023xxxx134434.dat

# 绘图
pip3 install matplotlib

# 清理 .dat 问题
mprof clean

pympler

pympler 支持内存占用

from pympler import tracker

tr = tracker.SummaryTracker()

# 打印两次 summary 的 diff
tr.print_diff()

tracemalloc

  • tracemalloc 模块是一个调试工具,用于跟踪 Python 分配的内存块,使用参考

  • trace 模块

  • 显示分配最多内存的 10 个文件

import tracemalloc

tracemalloc.start()

# ... run your application ...

snapshot = tracemalloc.take_snapshot()
top_stats = snapshot.statistics('lineno')

print("[ Top 10 ]")
for stat in top_stats[:10]:
    print(stat)

其他工具

psutil

pip install psutil

# 查看 python 进程占用的系统内存 RSS
pyrasite-shell <pid>
>>> import psutil, os
>>> psutil.Process(os.getpid()).memory_info().rss

guppy

  • guppy 取得内存使用的各种对象占用情况
$ pip install guppy

# 代码
from guppy import hpy
h = hpy()

h.heap()
h.iso(1,[],{})

resource 限制 python 进程的内存使用量

超过内存时,会被 kill,示例

import psutil
import resource
import time
​
p = psutil.Process()
print(p.pid)
​
def limit_memory(maxsize):
    soft, hard = resource.getrlimit(resource.RLIMIT_AS)
    resource.setrlimit(resource.RLIMIT_AS, (maxsize, hard))
​
limit_memory(1024*1024*180)   # 限制180M ,可申请内存,对应虚拟内存
​
lst = []
while True:
    lst.append("a"*1000000)
    time.sleep(0.5)

错误示例

  • uncollectible.py 使用 python2.7 可以看到资源没有释放
  • 循环引用的链上某个对象定义 __del__ 方法
  • 文件没有关闭
Home Archives Categories Tags Statistics
本文总阅读量 次 本站总访问量 次 本站总访客数