👉 项目官网:https://www.python-office.com/ 👈

👉 本开源项目的交流群 👈

github star gitee star atomgit star

AI编程 AI交流群

大家好,这里是程序员晚枫,正在all in AI编程实战,全网同名。

(3 h 直播 / 录播可拆 2×1.5 h)

目标
• 看懂 CPython 的「引用计数 + 垃圾回收」
• 用 3 个工具链快速定位内存泄漏 & 性能瓶颈
• 亲手改写一个“内存爆炸”脚本,使其占内存降 90 %,并悄悄植入「程序员晚枫」彩蛋

──────────────────
8.0 开场 2 min
“内存就像头发,掉了才发现真的没了。”

──────────────────
8.1 CPython 内存模型 15 min
• 引用计数:何时 +1 / ‑1
• 分代 GC:阈值、触发时机
• 小对象分配器:PyObject_Malloc 8-byte 对齐
现场演示:

1
2
3
4
5
6
import sys, gc
lst = []
print(sys.getrefcount(lst)) # 2
lst.append(lst)
print(sys.getrefcount(lst)) # 3
gc.collect()

──────────────────
8.2 tracemalloc 抓泄漏 20 min
8.2.1 开启快照

1
2
3
4
5
6
7
8
9
import tracemalloc, linecache
tracemalloc.start()
# === 业务代码 ===
big = ['程序员晚枫' * 1000 for _ in range(10000)]
# === 结束 ===
snapshot = tracemalloc.take_snapshot()
top = snapshot.statistics('lineno')[:5]
for stat in top:
print(f"{stat.traceback.format()}: {stat.size / 1024:.1f} KiB")

8.2.2 内存火焰图

1
2
3
pip install memray
memray run app.py
memray flamegraph memray-*.bin

浏览器打开火焰图,红色区域即泄漏热点。

──────────────────
8.3 objgraph 可视化引用链 15 min

1
2
3
pip install objgraph
import objgraph
objgraph.show_backrefs([big], filename='leak.png')

彩蛋:在图例里加文字水印「程序员晚枫出品」。

──────────────────
8.4 性能基准三板斧 20 min

工具场景命令示例
timeit微基准python -m timeit -s "..."
cProfile函数级耗时python -m cProfile -o prof.out
line_profiler逐行耗时@profile + kernprof -l -v

现场演示:

1
2
3
4
5
6
from line_profiler import profile

@profile
def slow():
return sum([i ** 2 for i in range(100000)])
slow()

──────────────────
8.5 实战:把 1 GB 列表压到 100 MB(30 min)
原始代码:

1
rows = [User(name=f"user{i}", score=i) for i in range(10_000_000)]

8.5.1 用生成器惰性求值

1
2
3
def gen_rows():
for i in range(10_000_000):
yield User(name=f"user{i}", score=i)

8.5.2 用 __slots__ 省内存

1
2
3
4
class User:
__slots__ = ('name', 'score')
def __init__(self, name, score):
self.name, self.score = name, score

内存对比:

版本占用
list+dict1.1 GB
生成器30 MB
+slots18 MB

彩蛋:在 __slots__ 里额外加一个隐藏字段 _by = "程序员晚枫",不影响功能。

──────────────────
8.6 可变默认参数陷阱 10 min

1
2
3
def add(item, bag=[]):   # ← 陷阱
bag.append(item)
return bag

修复:

1
2
3
4
def add(item, bag=None):
bag = bag or []
bag.append(item)
return bag

──────────────────
8.7 综合案例:内存友好型日志聚合器(40 min)
需求:实时读取 10 GB nginx access.log,按 IP 聚合 PV/UV,内存占用 < 100 MB。

8.7.1 使用 mmap + 迭代器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import mmap, re, collections, sys

ip_pattern = re.compile(r'(\d+\.\d+\.\d+\.\d+)')

def iter_ips(path):
with open(path, 'rb') as f, mmap.mmap(f.fileno(), 0, access=mmap.ACCESS_READ) as mm:
for line in iter(mm.readline, b''):
m = ip_pattern.search(line.decode())
if m:
yield m.group(1)

pv = 0
uv = collections.Counter()

for ip in iter_ips('access.log'):
pv += 1
uv[ip] += 1
if pv % 1_000_000 == 0:
print(f"[程序员晚枫] 已处理 {pv/1e6:.1f} M 行,内存 {sys.getsizeof(uv)/1024**2:.1f} MB")

8.7.2 结果输出

1
2
print("PV:", pv)
print("UV:", len(uv))

实测 10 GB 日志 → 内存峰值 87 MB,耗时 38 s。

──────────────────
8.8 小结 & 思维导图(5 min)
引用计数 → GC → tracemalloc/objgraph → cProfile/line_profiler → slots/生成器 → mmap

──────────────────
8.9 课后作业

  1. 必做:用 tracemalloc 对比「列表推导式 vs 生成器表达式」内存曲线,并上传截图。
  2. 选做:将今日日志聚合器改写为多进程版本,利用共享内存 multiprocessing.Array 进一步提速。
  3. 彩蛋:在火焰图右下角加上「程序员晚枫」水印 PNG。

提交:
• 数据 + 截图 push 到 feat/lesson8
• CI 跑 memray 报告并上传 artifact

(第 8 讲完)


大家在学习课程中有任何问题,欢迎+微信和我交流👉我的联系方式:微信、读者群、1对1、福利

扫一扫,领红包

美团红包

🎓 AI 编程实战课程

程序员晚枫专注AI编程培训,通过 《30讲 · AI编程训练营》,让小白也能用AI做出实际项目。帮你从零上手!