性能工程技能 (Performance Engineering Skill)
快速规则(日常开发时自动加载,只需读到这里)
[性能核心清单] ① 循环内禁止IO/网络/数据库调用(N+1→批量查询) ② 列表>100条必须虚拟化/分页 ③ 启动路径禁止同步网络请求 [内存三禁] ❌循环引用(闭包捕获self/delegate强引用) ❌大图不压缩直接加载 ❌无限缓存无淘汰策略 [缓存铁律] 读多写少=Cache+TTL+主动失效,写多读少=不缓存,❌缓存带认证的个人数据到公共层
写/改涉及性能的代码时,强制遵守:
- N+1检测:循环内有数据库查询/网络请求/文件IO→必须改为批量操作。Grep
for/forEach/map内的查询调用 - 列表性能:数据量>100条→虚拟化(LazyVStack/RecyclerView/虚拟滚动);>1000条→必须分页+游标
- 图片优化:必须懒加载+适当分辨率+缓存。大图(>1MB)必须压缩/缩略图。禁止主线程解码大图(解码大图耗时可达数百毫秒,主线程阻塞>16ms就掉帧)
- 启动优化:启动路径上禁止同步网络请求/大文件读取/重计算(启动超过3秒用户感知卡死,同步操作直接阻塞主线程)。能延迟的延迟,能异步的异步
- 内存管理:闭包捕获检查循环引用(Swift用
[weak self]),定时器/观察者必须在deinit中取消 - 缓存策略:明确TTL+最大容量+淘汰策略(LRU)。写入时清除相关缓存。❌无限增长的缓存
- 主线程保护:IO/网络/重计算禁止在主线程(主线程阻塞>16ms就掉帧,>3s用户感知卡死)。UI更新必须在主线程。检查
DispatchQueue.main使用正确性 - 批量操作:批量写入有数量上限(防OOM),大数据处理用流式/分批,❌一次加载全部到内存
完整审查流程(手动 /perf-engineering 或专项审查时执行)
Phase 1: 性能现状扫描
识别性能关键路径:
- 应用启动流程(从main到首屏可交互)
- 核心用户操作(最频繁的3-5个操作)
- 数据密集操作(列表加载/搜索/导出/同步)
- 后台任务(定时同步/推送处理/数据清理)
扫描性能风险模式(Grep搜索):
- 循环内的IO/网络/数据库调用
DispatchQueue.main.sync(主线程死锁风险)- 大数据量无分页的查询
- 未使用缓存的重复计算/请求
- 图片加载无压缩/无缓存
Phase 2: CPU性能审查
计算密集型操作:
- 排序/过滤/搜索算法复杂度是否合理(O(n²)→O(n log n))
- 正则表达式是否有灾难性回溯风险
- JSON解析/序列化是否在主线程
- 加密/哈希操作是否阻塞UI
主线程占用:
- Grep所有主线程操作,识别耗时操作
- 文件IO/数据库查询/网络请求是否在后台线程
- UI更新是否批量处理(而非逐个刷新)
- 动画帧率是否受后台任务影响
Phase 3: 内存性能审查
内存泄漏检测模式:
- 循环引用:闭包捕获
self/delegate强引用/Timer持有target - 未释放资源:未关闭的文件句柄/数据库连接/网络会话
- 观察者泄漏:NotificationCenter/KVO未移除观察者
- 缓存无限增长:内存缓存无最大容量限制/无淘汰策略
- 循环引用:闭包捕获
内存使用优化:
- 大数据集是否用分页/流式处理(而非全部加载到内存)
- 图片是否按显示尺寸缩放(而非加载原图)
- 临时大对象是否用
autoreleasepool(ObjC/Swift) - 是否有不必要的数据副本(值类型大对象频繁复制)
Phase 4: IO与网络性能
网络优化:
- 请求合并:相同数据的多个请求→合并为一个
- 预取策略:用户即将需要的数据提前加载
- 压缩:请求/响应是否启用gzip/brotli
- 连接复用:HTTP/2/Keep-Alive/WebSocket长连接
- 超时设置:连接超时/读超时/总超时是否合理
磁盘IO优化:
- 写入合并:高频小写入→批量写入
- 读取缓存:频繁读取的文件→内存缓存
- 序列化格式:JSON vs Protobuf vs SQLite(按场景选择)
- 文件大小:配置文件/缓存文件是否有大小限制
Phase 5: 数据库性能
查询性能:
- 慢查询识别:无索引的WHERE/JOIN/ORDER BY
- N+1查询:循环中逐条查询→批量查询(IN/JOIN)
- 大偏移分页:
OFFSET 10000→游标分页(WHERE id > last_id) SELECT *→只查需要的字段- 子查询→改为JOIN或EXISTS
写入性能:
- 批量插入:逐条INSERT→批量INSERT/事务包裹
- 索引维护成本:频繁更新的字段是否有不必要的索引
- 事务范围:是否最小化(长事务=长时间锁表)
Phase 6: 渲染与UI性能
列表/滚动性能:
- 大列表是否使用虚拟化(LazyVStack/UICollectionView/RecyclerView/虚拟滚动)
- Cell复用是否正确实现
- 滚动时是否有不必要的布局计算
- 图片是否异步加载+占位图
渲染优化:
- 不必要的重绘:状态变化→是否触发全量重绘(应为局部更新)
- 离屏渲染:圆角+阴影+裁剪组合(iOS上触发离屏渲染)
- 透明度叠加:多层半透明视图的混合开销
- 动画性能:是否使用GPU加速的属性(transform/opacity vs layout属性)
Phase 7: 启动性能
启动耗时分析:
- 冷启动 vs 热启动
- 启动阶段拆分:进程创建→dylib加载→main()→首屏渲染→可交互
- 各阶段耗时估算和优化空间
启动优化清单:
- 延迟初始化:非首屏需要的模块延迟到使用时初始化
- 预加载:首屏数据用缓存/本地数据先展示,后台刷新
- 减少dylib数量(iOS/macOS):合并小框架
- 启动任务分级:必须(首屏依赖) > 尽快(用户可能需要) > 延迟(后台即可)
Phase 8: 缓存策略审查
- 缓存分层:
| 层级 | 存储 | 速度 | 容量 | 适用场景 |
|---|---|---|---|---|
| L1 内存缓存 | Dictionary/NSCache | 纳秒 | 小(≤50MB) | 高频读取的小数据 |
| L2 磁盘缓存 | 文件/SQLite | 毫秒 | 中(≤500MB) | 图片/API响应/计算结果 |
| L3 网络缓存 | CDN/HTTP Cache | 秒级 | 大 | 静态资源/公共数据 |
- 缓存一致性:
- 写入时主动清除相关缓存(而非只依赖TTL过期)
- 缓存key设计:包含版本/用户ID/参数hash(防止脏数据)
- 降级策略:缓存失效时的兜底方案(返回旧数据+提示/loading)
Phase 9: 输出报告
## 性能审查报告
### 性能风险概览
| # | 位置(文件:行号) | 问题类型 | 描述 | 影响 | 优化方案 | 优先级 |
### N+1查询
| # | 文件:行号 | 循环体 | 查询内容 | 优化方案(批量查询) | 预期提升 |
### 内存风险
| # | 文件:行号 | 类型(泄漏/过大/未释放) | 描述 | 修复方案 | 优先级 |
### 启动优化建议
| 阶段 | 当前耗时(估) | 优化方案 | 预期提升 |
### 缓存策略建议
| 数据类型 | 当前策略 | 建议策略 | 预期效果 |
### 渲染性能
| 组件 | 问题 | 优化方案 | 预期效果 |
性能开发规则(写代码时强制遵守)
写代码时强制检查
- ❌ 禁止循环内IO/网络/数据库调用(100条数据=100次查询,批量只需1次,性能差100倍)
- ❌ 禁止主线程同步IO/网络/重计算(主线程阻塞>16ms就掉帧,>3s用户感知卡死)
- ❌ 禁止无限增长的缓存/列表(用户长时间使用后内存持续膨胀直到OOM崩溃)
- ❌ 禁止加载原图到内存(4K照片解码后占~48MB内存,10张=480MB,压缩+缩放后只需几MB)
- ❌ 禁止
SELECT *+无分页(数据增长后单次返回百万行,耗尽内存和带宽) - ✅ 大列表必须虚拟化(>100条)
- ✅ 启动路径延迟加载非必需模块
- ✅ 闭包捕获检查循环引用
- ✅ 批量操作有数量上限防OOM
性能分析工具速查
Go
go test -bench=. -benchmem ./... # 基准测试+内存分配
go test -cpuprofile=cpu.prof -memprofile=mem.prof # 生成profile
go tool pprof -http=:8080 cpu.prof # 可视化分析
curl http://localhost:6060/debug/pprof/goroutine?debug=1 # goroutine泄漏
GODEBUG=gctrace=1 ./app # GC追踪
前端(浏览器)
// 测量函数执行时间
console.time('render'); doSomething(); console.timeEnd('render');
// Performance API
performance.mark('start'); doWork(); performance.mark('end');
performance.measure('work', 'start', 'end');
// 长任务检测
new PerformanceObserver(list => console.log(list.getEntries()))
.observe({ entryTypes: ['longtask'] });
数据库慢查询
-- MySQL慢查询分析
EXPLAIN ANALYZE SELECT ...; -- 执行计划+实际耗时
SHOW PROCESSLIST; -- 当前连接
SELECT * FROM sys.statements_with_runtimes_in_95th_percentile; -- P95慢查询
常见优化模式速查
| 问题 | 症状 | 优化方案 | 预期提升 |
|---|---|---|---|
| N+1查询 | 循环内SELECT | JOIN/IN批量查询 | 10-100x |
| 无索引 | 全表扫描 | 添加合适索引 | 10-1000x |
| 大JSON序列化 | 响应慢 | 只返回必要字段/分页 | 2-10x |
| 无缓存热数据 | DB压力大 | Redis/内存缓存+TTL | 5-50x |
| 同步阻塞IO | 主线程卡死 | goroutine/async/Worker | 消除卡顿 |
| 大列表渲染 | 页面卡顿 | 虚拟滚动/分页 | 消除卡顿 |
| 全量导入库 | 首屏慢 | 按需导入/Tree-shaking | 减少30-70%体积 |
| 未压缩图片 | 加载慢 | WebP+lazy load+CDN | 减少50-80%体积 |
| 频繁GC | 吞吐下降 | 对象池/减少分配/sync.Pool | 20-50% |
| 锁竞争 | 并发瓶颈 | 分段锁/无锁结构/channel | 2-10x |
约束
- 性能建议必须附带 文件:行号 引用
- 优化方案必须附带预期提升估算(如"减少
200ms"/"内存降低30%") - 不做过早优化——先证明是瓶颈再优化
- 优化不得牺牲代码可读性(除非是关键热路径)
- 缓存策略必须考虑一致性问题,不只追求速度