延迟加载 Lazy Loading——性能优化的核心手法
目标:学会一个通用、可迁移、面试高频的性能优化思维:巨量的东西别一次性全加载,"要用到的那一刻"才加载。 这是从 yt-dlp 懒加载(1800 个网站模块只花 1 个轻壳文件)里拎出来的最值钱的一课。
一、本质一句话
把"重的东西"的『身份证』和『本人』分开——先只登记身份证,用到本人那一刻,才去把本人弄进来。
- 身份证 = 轻量判断信息(这个 URL 归你管吗 / 这个文件叫什么)
- 本人 = 真实的重资源(整个站点的解析代码 / 大图 / 整页 JS / 整张表)
启动/初始化时只加载「身份证列表」,真正需要"本人"时才加载。
二、这个思路用得满身都是(不限于 yt-dlp)
| 场景 | 普通做法(慢) | 懒加载做法(快) |
|---|---|---|
| 1800 个网站模块 | 启动全 import → 慢、占内存 | 只登记"名字+判断规则",用到才 import |
| 网页图片懒加载 | 打开页面就全下 | 滚动到才下 |
| 前端路由懒加载 | 进站就把整个应用 JS 下完 | 切换到哪个页面才下哪个 |
| 大数据库 | 启动就连上并查出所有表 | 用到某张表才查 |
| 大型配置文件 | 启动全读全解析 | 用到某段才读 |
[!tip] 看到规律了吗?凡是"东西很多 / 单个很重",都可能吃到懒加载的红利。 关键是问一句:这个资源"现在真的需要"吗?不需要就先别加载。
三、yt-dlp 是怎么做的(实例)
问题:1800 个网站提取器,每个一个文件,启动全加载会卡死。
解法:启动只加载一个薄壳 lazy_extractors.py,里面是 1800 个"轻量桩"。
[启动]
lazy_extractors.py(薄壳)
每个站点:只有"身份证"
_VALID_URL ← 判断"这个URL归我管吗"用的正则
IE_NAME ← 我叫什么
suitable() ← 判断方法(轻)
(不含真正干活的解析代码!)
[真要用到某站时]
real_class 触发 → importlib.import_module(该站真实模块)
→ 此刻才加载"本人"
关键功夫:判断"URL 归哪站"这一步,只靠 _VALID_URL 正则(轻),不需要加载整个解析逻辑(重)。所以能推迟到真正要解析时才 import。
这就是第一条里说的"分离『判断所需』和『执行所需』"——判断只用身份证,执行才要本人。
四、你自己怎么写这种优化(可抄模板)
核心三件套:① 登记薄壳 / ② 按需触发 / ③ 缓存。
# === 1) 轻量"身份证/薄壳"登记 ===
# 假设有 1000 个需要按名字加载的"处理器"
_REGISTRY = {} # {名前缀: 模块路径}
def register(name, module_path):
_REGISTRY[name] = module_path # 只登记,不加载
# === 2) 真正要用到时才加载 + 缓存 ===
_cache = {}
def get_handler(name):
if name not in _cache: # 没加载过?
mod = importlib.import_module(_REGISTRY[name]) # 此刻才 import(重)
_cache[name] = mod # 缓存,避免重复加载
return _cache[name]
# 用的时候才真正建立 —— 平时一个 handler 都不会被 import
handlers = {name: get_handler(n) for n in want_now}
[!warning] 一个纪律:判断/登记函数里绝不能"顺便 import 别人"。一旦在"该轻的地方"偷偷加载了重东西,整个懒加载就白搭了,还会破坏缓存。
五、什么时候该用 / 什么时候别硬用
| 该用 ✅ | 别硬用 ❌ |
|---|---|
| 资源多、启动慢、内存吃紧 | 你总共才 2-3 个模块 |
| 有"轻量判断能不能替代全量"的机会 | 所有内容启动就都要用 |
| 想加快首屏/启动速度 | 带来复杂度超过收益 |
[!tip] 好判断标准:"现在真的有人要用它吗?" 如果大部分时候用不到,懒加载就有价值;如果启动后全都会用到,那它还是一次性加载更简单。
六、一句话带走
"东西又重又多?别一次全加载——先给个轻量『身份证』缓存,等真用到的那一刻再
import。" 这就是懒加载,也是你以后优化性能、面试讲优化的最该先想到的一招。
关联:这套思想在 yt-dlp 里的完整实现见学习库 → [[Startup-and-Registration]];它支撑的"1800+ 网站可插拔"体系见 [[Extractor-Subsystem]]。