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,当垃圾回收时会清空内存
- 垃圾回收时,会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
示例
@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
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
$ 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)
错误示例