编程进阶网 编程进阶网
首页
  • 计算机原理
  • 操作系统
  • 网络协议
  • 数据库原理
  • 面向对象
  • 设计原则
  • 设计模式
  • 系统架构
  • 性能优化
  • 编程原理
  • 方案设计
  • 稳定可靠
  • 工程运维
  • 基础认知
  • 线性结构
  • 树与哈希
  • 工业级实现
  • 算法思想
  • 实战与综合
  • 算法题考核
  • C语言入门
  • C综合案例
  • C专栏博客
  • C标准集库
  • C++入门教程
  • C++综合案例
  • C++专栏博客
  • C++开发技巧
  • Java入门教程
  • Java综合案例
  • Java专栏博客
  • Go入门教程
  • Go综合案例
  • Go专栏博客
  • Go开发技巧
  • JavaScript入门
  • JavaScript高级
  • Android库解读
  • Android专栏
  • Android智能硬件
  • iOS ObjC入门
  • iOS Swift入门
  • iOS入门精通
  • Web之Html手册
  • Web之TypeScript
  • Web之Vue高级进阶
  • Linux之QML入门
  • Linux之QT核心库
  • Linux实践开发
  • Python教程
  • Shell&Bash教程
  • 工具脚本
  • 自动化脚本
  • 质量保障
  • 产品思考
  • 软实力
  • 开发流程
  • Git应用
  • 技术模版
  • 技术规范
  • Markdown
  • Mermaid
  • 开源协议
  • JSON工具
  • 文本工具
  • 图片处理
  • 文档转化
  • 代码压缩
  • 关于我
  • 自我精进
  • 职场管理
  • 职场面试
  • 心情杂货
  • 友情链接

杨充

专注编程 · 终身学习者
首页
  • 计算机原理
  • 操作系统
  • 网络协议
  • 数据库原理
  • 面向对象
  • 设计原则
  • 设计模式
  • 系统架构
  • 性能优化
  • 编程原理
  • 方案设计
  • 稳定可靠
  • 工程运维
  • 基础认知
  • 线性结构
  • 树与哈希
  • 工业级实现
  • 算法思想
  • 实战与综合
  • 算法题考核
  • C语言入门
  • C综合案例
  • C专栏博客
  • C标准集库
  • C++入门教程
  • C++综合案例
  • C++专栏博客
  • C++开发技巧
  • Java入门教程
  • Java综合案例
  • Java专栏博客
  • Go入门教程
  • Go综合案例
  • Go专栏博客
  • Go开发技巧
  • JavaScript入门
  • JavaScript高级
  • Android库解读
  • Android专栏
  • Android智能硬件
  • iOS ObjC入门
  • iOS Swift入门
  • iOS入门精通
  • Web之Html手册
  • Web之TypeScript
  • Web之Vue高级进阶
  • Linux之QML入门
  • Linux之QT核心库
  • Linux实践开发
  • Python教程
  • Shell&Bash教程
  • 工具脚本
  • 自动化脚本
  • 质量保障
  • 产品思考
  • 软实力
  • 开发流程
  • Git应用
  • 技术模版
  • 技术规范
  • Markdown
  • Mermaid
  • 开源协议
  • JSON工具
  • 文本工具
  • 图片处理
  • 文档转化
  • 代码压缩
  • 关于我
  • 自我精进
  • 职场管理
  • 职场面试
  • 心情杂货
  • 友情链接
  • README
  • 性能优化实践

    • README
    • 公共方法论

    • 体系建设篇

    • 资源专项篇

      • CPU监控与分析
        • 00.阅读说明
        • 00.5 贯穿案例:直播礼物墙卡顿
          • 0.5.1 问题背景
          • 0.5.2 初步排查(错误的假设)
          • 0.5.3 案例任务(贯穿目标)
        • 01.问题域定义
          • 1.1 现象与代价
          • 1.2 度量准则
          • 1.3 行业基准与目标值
          • 1.4 反直觉问题清单
          • ▶▶ 案例回扣 1(重定义"卡顿")
        • 02.第一性原理
          • 2.1 CPU 的物理本质
          • 2.2 时间片分配模型
          • 2.3 异构多核架构
          • 2.4 跨平台同构原理
          • ▶▶ 案例回扣 2(用黄金公式拆每帧 CPU 预算)
        • 03.度量与采集
          • 3.1 三种采集方案的本质
          • 3.2 利用率与负载之别
          • 3.3 跨平台采集对照表
          • 3.4 数据可信度评估
        • 04.归因方法
          • 4.1 CPU 异常归因决策树
          • 4.2 利用率高的根因拆解
          • 4.3 利用率低却卡的归因
          • 4.4 IPC 与缓存归因
          • ▶▶ 案例回扣 3(沿决策树定位根因)
        • 05.求证实验 ⭐
          • 5.1 实验一:大小核耗时差异
          • 5.2 实验二:上下文切换代价
          • 5.3 实验三:缓存友好布局
          • 5.4 实验四:伪共享的隐形代价
          • 5.5 实验五:分支预测失败的代价
          • 5.6 五大实验启示
          • ▶▶ 案例回扣 4(实验数据回扣礼物墙)
        • 06.优化策略
          • 6.1 指令数维度:让 CPU 少做事
          • 6.1.1 算法降阶(O(n²) → O(n log n))
          • 6.1.2 异步化(不是降总量,是把总量错开)
          • 6.1.3 序列化协议升级(JSON → Protobuf / FlatBuffers)
          • 6.1.4 反射/正则缓存
          • 6.2 IPC 维度:让 CPU 干得更高效
          • 6.2.1 数据布局:AoS → SoA
          • 6.2.2 SIMD / NEON 向量化
          • 6.2.3 减少分支(branch-less 编程)
          • 6.2.4 cache line 对齐 + 伪共享治理
          • 6.3 频率与调度维度:让 CPU 跑得更快
          • 6.3.1 关键路径绑大核
          • 6.3.2 race-to-idle 调度
          • 6.3.3 线程池大小科学化
          • 6.3.4 锁优化 / 无锁数据结构
          • 6.4 平台特化策略
          • 6.5 优先级判定(ROI 公式)
          • ▶▶ 案例回扣 5(礼物墙的优化执行栈)
        • 07.实战案例
          • 7.1 礼物墙优化最终结果
          • 7.1.1 优化前后核心指标
          • 7.1.2 五项优化各自贡献
          • 7.1.3 业务回归
          • 7.1.4 灰度与防劣化
          • 7.2 跨端同构案例:JSON → Protobuf
          • 7.3 平台特异案例:Android 启动期小核陷阱
        • 08.防劣化与长效治理
          • 8.1 三道防线总览
          • 8.2 编码期 Lint
          • 8.3 CI 卡口与线上 SLO
        • 09.跨平台对照速查
          • 9.1 工具速查
          • 9.2 关键 API 速查
        • 10.方法论沉淀
          • 10.1 五条核心原则
          • 10.2 五个常见误区
          • 10.3 贯穿案例的方法论提炼
          • 10.4 延伸阅读
        • 11.探索性思考:CPU 性能的"反直觉"再追问
          • 11.1 为什么"CPU 利用率 100%"有时是好事
          • 11.2 为什么"多核"不等于"快 N 倍"
          • 11.3 为什么"big.LITTLE 大核"未必更快
          • 11.4 为什么"缓存优化"是 IPC 提升的最大杠杆
          • 11.5 为什么"温度"是性能的隐形杀手
          • 11.6 反直觉问题清单的最终回应
        • 12.演进展望:CPU 性能监控的下一个五年
          • 12.1 PMU 数据普及到客户端
          • 12.2 异构 CPU 架构(ARMv9 / AppleSilicon)
          • 12.3 eBPF 从内核到用户态
          • 12.4 LLM 辅助归因
          • 12.5 协程 / 异步运行时的 CPU 模型
        • 13.跨段权衡哲学:CPU 优化的"零和博弈"地图
          • 13.1 七大经典权衡
          • 13.2 决策的"三问法"
        • 14.错误模式库:30 个 CPU 反模式速查
          • 14.1 算法 / 数据结构反模式(10 项)
          • 14.2 并发 / 锁反模式(5 项)
          • 14.3 缓存 / 数据布局反模式(5 项)
          • 14.4 调度 / Affinity 反模式(5 项)
          • 14.5 监控 / 度量反模式(5 项)
        • 15.ROI 决策框架:CPU 优化的"先后顺序"
          • 15.1 优化项 ROI 排序模板
          • 15.2 反向不该做的优化
        • 16.组织协同模式:CPU 优化是团队工程
          • 16.1 四方角色
          • 16.2 性能预算机制
          • 16.3 周度雷达
        • 17.可访问性与 CPU:被忽视的维度
          • 17.1 无障碍服务的 CPU 开销
          • 17.2 国际化的 CPU 隐藏成本
          • 17.3 老旧机型兼容
        • 18.嵌入式与异构平台特化
          • 18.1 车机 / HMI
          • 18.2 IoT / MCU
          • 18.3 桌面 / Electron
        • 19.自检清单
          • 19.1 设计阶段(10 项)
          • 19.2 编码阶段(10 项)
          • 19.3 测试阶段(10 项)
          • 19.4 上线阶段(10 项)
        • 20.哲学迁移:CPU 思维的普适性
          • 20.1 CPU 三因素 vs 一切性能问题
          • 20.2 缓存 vs 内存层级 vs 系统层级
          • 20.3 调度 vs 资源分配
          • 20.4 元启示
        • 21.一句话哲学
      • 内存监控与治理
      • OOM与低内存治理
      • 线程模型调度优化
      • 进程与多进程优化
      • IO与存储性能
    • 流水线专项

    • 业务专项篇

    • 交付防御篇

  • 程序编程原理

  • 稳定性与可靠性

  • 工程化与运维

  • 方案设计思想

  • 专栏
  • 性能优化实践
  • 资源专项篇
杨充
2026-05-27
目录

CPU监控与分析

# CPU 监控与分析

📊 学习成本预估 | 难度:⭐⭐⭐⭐⭐(5/5)| 阅读:约 45 分钟 | 实操:3 小时 🔗 前置阅读:卷零·01, 03, 05 | ➡️ 后续延伸:卷二·04, 卷四·01

本文是《性能优化实践 · 卷二 资源篇》的开篇。
CPU 是性能问题最早被怀疑、也最容易被误解的资源。本文用第一性原理重写它的全部认知。

# 目录介绍

  • 00.阅读说明
  • 00.5 贯穿案例:直播礼物墙卡顿
  • 01.问题域定义
    • 1.1 现象与代价
    • 1.2 度量准则
    • 1.3 行业基准与目标值
    • 1.4 反直觉问题清单
  • 02.第一性原理
    • 2.1 CPU 的物理本质
    • 2.2 时间片分配模型
    • 2.3 异构多核架构
    • 2.4 跨平台同构原理
  • 03.度量与采集
    • 3.1 三种采集方案的本质
    • 3.2 利用率与负载之别
    • 3.3 跨平台采集对照表
    • 3.4 数据可信度评估
  • 04.归因方法
    • 4.1 CPU 异常归因决策树
    • 4.2 利用率高的根因拆解
    • 4.3 利用率低却卡的归因
    • 4.4 IPC 与缓存归因
  • 05.求证实验 ⭐
    • 5.1 实验一:大小核耗时差异
    • 5.2 实验二:上下文切换代价
    • 5.3 实验三:缓存友好布局
    • 5.4 实验四:伪共享的隐形代价
    • 5.5 实验五:分支预测失败的代价
    • 5.6 五大实验启示
  • 06.优化策略
    • 6.1 指令数维度:让 CPU 少做事
    • 6.2 IPC 维度:让 CPU 干得更高效
    • 6.3 频率与调度维度:让 CPU 跑得更快
    • 6.4 平台特化策略
    • 6.5 优先级判定(ROI 公式)
  • 07.实战案例
    • 7.1 礼物墙优化最终结果
    • 7.2 跨端同构案例:JSON → Protobuf
    • 7.3 平台特异案例:Android 启动期小核陷阱
  • 08.防劣化与长效治理
    • 8.1 三道防线总览
    • 8.2 编码期 Lint
    • 8.3 CI 卡口与线上 SLO
  • 09.跨平台对照速查
    • 9.1 工具速查
    • 9.2 关键 API 速查
  • 10.总结与延伸
    • 10.1 五条核心原则
    • 10.2 五个常见误区
    • 10.3 延伸阅读

# 00.阅读说明

  • 卷归属:卷二 资源篇
  • 目标层级:L1 系统层 / L2 运行时(适合 L2-L3 工程师)
  • 适用平台:Android / iOS / Web / 嵌入式 Linux / 桌面(统一原理 + 各端实现)
  • 前置阅读:
    • 卷零·01 性能工程总论(资源 × 时间 × 流水线模型)
    • 卷零·02 跨平台性能模型(USE / RED / APDEX)
    • 卷零·05 归因方法论(on-CPU vs off-CPU)
  • 本文核心命题:

CPU 不是"忙不忙"的问题,而是"忙得有效不有效"的问题。
优化 CPU 的本质,不是降低利用率,而是提高单位 CPU 时间的产出价值。

# 00.5 贯穿案例:直播礼物墙卡顿

本章用一个真实线上案例贯穿全文。后续每章会用 ▶▶ 案例回扣 标记回到这个案例,让方法论始终落在真实问题上。

# 0.5.1 问题背景

  • 业务场景:某直播 App 大型活动现场,主播收礼时屏幕同时滚动一面"礼物墙"——3 列网格、每秒新增 5-8 条记录、单条带礼物图标 + 用户头像 + 飘字动画。
  • 用户反馈:高峰期(2000+ 在线)礼物墙严重卡顿,FPS 从 58 跌到 12,大量用户投诉"礼物送不出去"。
  • 机型分布:千元机(中低端)反馈占 73%,旗舰机偶发。
  • 业务损失:高峰期送礼 GMV 同比下降 28%,是次重大故障。

# 0.5.2 初步排查(错误的假设)

工程团队第一反应:

假设 措施 结果
内存不够 加大缓存、减少对象创建 FPS 仅提升 2
网络拉胯 长连接改 WebSocket 弹幕到达更快但更卡
列表没复用 引入 RecyclerView 池化 中端机 +5 FPS,低端机无效

3 周时间,3 次发版,未解决问题。这是"经验主义"性能优化最典型的失败模式——没有用数据归因,凭直觉改代码。

# 0.5.3 案例任务(贯穿目标)

我们将在后续章节用本文方法论重新审视该案例:

章节 该章对本案例做什么
§01 问题域 重新定义"卡顿"——不是 FPS,而是"主线程 P95 帧时长 > 33ms"
§02 第一性原理 用 指令数 / IPC / 频率 拆解每帧的 CPU 预算
§03 度量采集 同时用 OS 时间片 + Profiler + PMU 三方法做交叉验证
§04 归因决策树 走"利用率高 → IPC 低"分支,定位到 cache miss
§05 求证实验 实验三的"缓存友好布局"直接对应本案例的根因
§06 优化策略 给出 4 个针对性优化(绑大核 + SoA + SIMD 飘字 + 无锁队列)
§07 实战收尾 完整列出优化前后数据:FPS 12 → 56,CPU% 95→62

读完本文,你将看到:同一个问题,"经验派" 3 周失败,"方法论派" 3 天解决。这就是本专栏的意义。


# 01.问题域定义

# 1.1 现象与代价

用户感知:

  • 设备发烫、风扇狂转(功耗代价的直接体感)
  • 操作响应迟缓 / 列表滚动卡顿(CPU 资源争抢)
  • 电量异常掉电(CPU 高频驻留)
  • 应用被系统降频或杀死(系统保护机制)
  • 后台任务跑不完(被调度限制)

业务代价(行业数据):

指标 影响 来源
设备发热 5℃,电量消耗 +20% 用户续航焦虑、卸载率上升 Google Android Vitals
CPU 持续 > 80%,应用 ANR 率提升 3× 崩溃率劣化 Play Console 公开数据
大核驻留时间过高,触发热降频 性能反向劣化 厂商 SoC 报告
海外低端机相比旗舰,同任务慢 4× 低端机用户流失 Facebook Building for Billions

# 1.2 度量准则

参考 卷零·02 跨平台性能模型与指标体系,本文从三个视角组合度量:

视角 模型 关键指标 含义
资源级 USE-U 进程 / 线程 CPU 利用率 资源占用
资源级 USE-S Run Queue 长度、上下文切换率 是否饱和
资源级 USE-E 调度延迟、降频次数、温度告警 错误信号
效率级 — IPC(每周期指令数)、Cache Miss Rate 单位 CPU 的产出效率
体感级 APDEX 主线程 on-CPU 占比、关键任务 P95 耗时 用户感知

⚠️ 不要只看 CPU 利用率。利用率高 ≠ 慢、利用率低 ≠ 快,需要配合饱和度(队列长度)和效率(IPC)综合判断。

# 1.3 行业基准与目标值

业界共识的 CPU 健康度分级:

等级 进程 CPU% 体感
健康 < 30% 流畅、不发热
注意 30%–60% 偶有抖动、温热
警告 60%–85% 明显发热、风扇启动
危险 > 85% 卡顿明显、可能降频
严重 持续 100% 触发热保护 / ANR

典型 SLO 目标(中端机基线,需按机型分档):

指标 目标值
主线程 on-CPU% P95 < 60%
进程 CPU% 均值 < 25%(前台)/ < 5%(后台)
上下文切换率 < 5000 次/秒(单线程)
Run Queue P95 < 核心数
IPC(典型业务) > 1.5(ARM A 系列)
降频会话占比 < 1%

# 1.4 反直觉问题清单

下面 8 个问题用于挑战经验主义认知,本文将逐一在后续章节给出可证伪的答案:

  1. CPU 利用率 50% 的 App 算不算有性能问题?(见 §1.2 / §4.3)
  2. CPU 100% 一定是 CPU 瓶颈吗?(见 §4.2 / §4.4)
  3. 多线程一定能加速程序吗?(见 §5.2)
  4. 同一段代码为什么在不同设备上耗时差几倍?(见 §5.1)
  5. CPU 利用率不高但用户卡顿,问题在哪?(见 §3.2 / §4.3)
  6. CPU 高就一定耗电吗?(见 §6.1)
  7. 绑大核就一定快吗?(见 §5.1)
  8. 顺序遍历数组真的比链表快吗?为什么?(见 §5.3)

# ▶▶ 案例回扣 1(重定义"卡顿")

回到礼物墙案例。原团队描述问题是"FPS 跌到 12"——但 FPS 是均值,无法指导优化。重定义:

错误描述 工程化描述
礼物墙卡 主线程 P95 帧时长 80ms,P99 帧时长 130ms
千元机更卡 主线程 on-CPU% 在低端机持续 > 95%
高峰更卡 用户达到 2000+ 时帧时长方差 σ > 35ms(抖动)

重定义的价值:

  • 从"卡"到"超过 33ms 的帧占比 X%"——可量化、可监控、可设 SLO
  • 从"FPS 跌"到"主线程 on-CPU 占满"——指向 CPU 是瓶颈,不是网络/内存
  • 从"低端机卡"到"低端机 IPC 异常低"——为 §05 缓存优化埋下伏笔

# 02.第一性原理

本节回答四个根本问题:①CPU 在物理上做了什么?②为什么"利用率"是一个会骗人的指标?③异构多核如何改变了"性能"的语义?④为什么所有平台 CPU 监控都同构?

# 2.1 CPU 的物理本质

CPU 究竟在做什么

CPU 在物理上只做一件事:不停地"取指令—解码—执行—回写"。这是一个无限循环,从机器加电开始,永远不停(除非进入睡眠态)。

  CPU 核心循环(指令流水线):

  ┌──────┐  ┌──────┐  ┌──────┐  ┌──────┐  ┌──────┐
  │ Fetch│→│Decode│→│Execute│→│ Mem  │→│Write │
  │ 取指 │  │ 解码 │  │ 执行 │  │ 访存 │  │ 回写 │
  └──────┘  └──────┘  └──────┘  └──────┘  └──────┘
   ~1 ns     ~1 ns     ~1-3ns    ~1-100ns  ~1 ns
1
2
3
4
5
6
7

关键观察:CPU 永远在跑。"空闲(idle)"不代表 CPU 停了,而是在执行系统的 idle 任务(通常是 wfi / hlt 这类省电指令)。所以任何时刻,CPU 都在做某件事;问题只是"做的事有没有用"。

性能的真正定义

由此引出 CPU 性能的真正定义:

  程序耗时 = 指令数 × 每指令时钟周期 × 单时钟周期时长
            (Instructions) × (CPI)        × (1 / Clock Frequency)

        = 指令数 / IPC × (1 / 频率)
1
2
3
4

这个公式被称为 CPU 性能黄金公式。优化 CPU 有且只有三条路:

  ┌──────────────────────────────────────────────────────┐
  │ ① 减少指令数  (Instructions ↓)                       │
  │    算法优化 / 数据结构优化 / 缓存结果 / 移除冗余        │
  ├──────────────────────────────────────────────────────┤
  │ ② 提高 IPC   (Instructions Per Cycle ↑)              │
  │    缓存友好 / 减少分支 / 利用 SIMD / 数据局部性         │
  ├──────────────────────────────────────────────────────┤
  │ ③ 提高频率   (Frequency ↑)                           │
  │    绑大核 / 提升优先级 / 避免热降频                    │
  └──────────────────────────────────────────────────────┘
1
2
3
4
5
6
7
8
9
10

真相:业界 90% 的"CPU 优化"都在做 ①,但 ② 和 ③ 在低端机和移动设备上往往收益更大(频率受热限制、IPC 受缓存限制)。

CPU 缓存的层级与代价

   CPU 核心
     │ 1 cycle (~0.3 ns)
     ▼
   寄存器(KB 级)
     │
     ▼
   L1 Cache (32-64 KB) ──── 4 cycles  (~1 ns)
     │
     ▼
   L2 Cache (256 KB-1 MB)── 12 cycles (~3 ns)
     │
     ▼
   L3 Cache (2-32 MB)  ──── 40 cycles (~12 ns)  共享
     │
     ▼
   主内存 RAM           ── 200 cycles (~60 ns)  慢 200 倍!
     │
     ▼
   SSD / Flash         ── 1M+ cycles (~100 μs)  慢 100 万倍!
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

关键洞察:

  • L1 命中和主内存访问差 200 倍。
  • 一次 cache miss 浪费的 CPU 时间,等于跑 200 条普通指令。
  • 因此"指令数少"不一定快,"内存访问少 / 局部性好"才更重要。这就是为什么数组顺序遍历 ≫ 链表随机访问(详见 §5.3 求证实验)。

# 2.2 时间片分配模型

"CPU 利用率"到底是什么

top / Activity Monitor / Task Manager 显示的"CPU 利用率",本质是时间片占用统计:

  CPU 时间 = user + nice + system + idle + iowait + irq + softirq + steal

  CPU 利用率 = (非 idle 时间) / 总时间
            = (user + nice + system + iowait + irq + softirq + steal) / 总时间
1
2
3
4

每一类时间的含义:

类型 物理意义 典型场景
user 用户态 CPU 时间 应用代码执行
nice 低优先级用户态 设了 nice 值的进程
system 内核态 CPU 时间 系统调用 / 驱动
idle CPU 空闲时间 没任务,跑 idle thread
iowait 等待 IO 完成的时间 磁盘 / 网络 IO 阻塞
irq 硬中断处理 网卡 / 触屏中断
softirq 软中断处理 网络包处理
steal 被虚拟机宿主抢占 云主机 / 容器场景

利用率的"骗局"

利用率有三个常见误读:

误读 1:利用率低 = 没问题

反例:进程 CPU% = 5%,但 user 时间高度集中在主线程的某 200ms 窗口内 → 用户感觉卡。均值掩盖了瞬时峰值。

误读 2:利用率高 = 有问题

反例:图像编码 / 加密 / 视频解码本来就是 CPU 密集,利用率 80% 是健康的。有效的高利用率是好事。

误读 3:iowait 高 = CPU 慢

实际:iowait 高代表 CPU 闲着等 IO,瓶颈在磁盘 / 网络,不是 CPU 不够用。错误归因会浪费优化资源。

这是后面 §3.2 必须把"利用率 vs 负载"专门拎出来讲的根因。

上下文切换的代价

操作系统通过调度器分配 CPU 时间片。每个线程跑到时间片用完(或主动让出)时,CPU 必须保存当前线程状态、恢复下一线程状态,这就是上下文切换。

  上下文切换的物理动作:
    1. 保存当前线程的所有寄存器 (~30 个,几十纳秒)
    2. 刷新 TLB(Translation Lookaside Buffer,地址翻译缓存)
    3. 切换内存映射(页表切换)
    4. 加载下一线程的寄存器
    5. 缓存(L1/L2)变成"冷的",需要重新填充

  单次开销:1-10 μs
  代价主要在:cache miss / TLB miss 引起的间接成本
1
2
3
4
5
6
7
8
9

每秒数万次上下文切换 → 累计开销可达 10–30% CPU 时间,这部分是纯浪费。这也是为什么 §5.2 实验要量化"多线程不是免费午餐"。

# 2.3 异构多核架构

移动端 CPU 的特殊性

桌面 CPU 的所有核都是同构的(频率、IPC 一致)。移动端 CPU 不是这样:

   典型移动 CPU 架构(big.LITTLE / DynamIQ):

   ┌────────────────────────────────────────────────────┐
   │                  CPU 集群                           │
   ├──────────┬──────────────┬──────────────────────────┤
   │ 超大核 ×1 │  大核 ×3     │  小核 ×4                 │
   │ Cortex-X4│ Cortex-A720  │  Cortex-A520             │
   │ 3.3 GHz  │ 2.3 GHz      │  1.9 GHz                 │
   │ 性能最强  │ 性能均衡      │  功耗最低                 │
   │ IPC ~3.5 │ IPC ~2.5     │  IPC ~1.5                │
   │          │              │                          │
   │ 适合:    │ 适合:        │ 适合:                    │
   │ UI 渲染   │ 图片解码      │ 后台同步                  │
   │ 复杂计算  │ 网络处理      │ 心跳保活                  │
   └──────────┴──────────────┴──────────────────────────┘
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

这对性能优化的颠覆性影响:

  1. 同一段代码在大核和小核上的耗时差 3–4 倍(详见 §5.1 求证实验)。
  2. 不能用单一基准衡量性能:测试机用旗舰大核跑出 50ms,到低端机小核可能是 200ms。
  3. 线程优先级影响调度核:Android 的 nice 值 + cgroup 决定线程跑在哪个核。
  4. 热感知调度:CPU 温度高了之后,即使你的线程优先级最高,也会被迁到小核——所谓"热降频陷阱"。
  5. DVFS(动态电压频率调节):CPU 频率随负载动态调整,空载时频率可能只有最高的 1/3——这是测耗时陷阱。

MESI 协议与伪共享

多核 CPU 共享主内存,但每个核有自己的 L1/L2 缓存。要保证数据一致性,必须用缓存一致性协议(MESI):

状态 含义 触发场景
Modified 已修改,与主内存不一致 当前核心写入了新值
Exclusive 独占,与主内存一致 只有当前核缓存了它
Shared 共享,多核都有副本 多核读取了同一数据
Invalid 无效,被其他核改了 其他核刚 Modified

伪共享(False Sharing)陷阱:缓存以"行"为单位(一般 64 字节)。两个线程修改不同变量,但变量恰好落在同一缓存行上,会导致缓存行在两个核之间反复失效和加载,性能急剧下降。

  线程 A(核 1)  →  写 var_a  →  缓存行 X 变为 Modified
  线程 B(核 2)  →  写 var_b  →  必须先让核 1 的缓存行 X 失效
                                    再加载到核 2,再写入

  即使两个变量根本无关,性能可能下降 10-50 倍!
1
2
3
4
5

这是多线程编程最隐蔽的性能杀手之一。Java 用 @Contended 注解、C++ 用对齐 padding 来规避。

# 2.4 跨平台同构原理

所有支持多任务的操作系统(Linux/Android/Darwin/iOS/嵌入式 RTOS),都必须解决同一个问题:

如何在有限的 CPU 核上,公平、高效地执行多个任务?

这个目标决定了它们必然演化出同一种监控接口:

  通用 CPU 监控模型:

  ① 时间片累加器(每个进程 / 线程在每种状态下累计了多少时间)
        │
        ▼
  ② 周期采样(每隔一段时间读两次累加器,差值就是这段时间的 CPU 占用)
        │
        ▼
  ③ 状态分类(user / system / idle / iowait...)
        │
        ▼
  ④ 调度信息(队列长度、上下文切换次数、调度延迟)
1
2
3
4
5
6
7
8
9
10
11
12

每个平台都必须暴露这四类信息:

信息 Linux/Android macOS/iOS Web 嵌入式
时间片累加器 /proc/stat host_processor_info Performance.now(间接) /proc/stat
进程级 /proc/[pid]/stat task_info Performance API /proc/[pid]/stat
线程级 /proc/[pid]/task/[tid]/stat thread_info 受限 /proc/[pid]/task
队列 / 调度 /proc/loadavg host_load_info 不可见 /proc/loadavg

同构带来的工程价值:

  1. 指标语言可迁移:在任意一端学会"利用率 / 负载 / 上下文切换 / IPC",迁移到其他端零成本。
  2. 采集代码同构:Android / 嵌入式 Linux 共享 /proc 接口,iOS / macOS 共享 Mach 接口。
  3. 优化思路一致:减少指令数 / 提升 IPC / 提升频率三条路在所有平台都成立。

跨平台的差异只在 API 名字和访问限制上,原理永远是一样的。

# ▶▶ 案例回扣 2(用黄金公式拆每帧 CPU 预算)

把礼物墙的"60fps = 16.67ms 每帧"用 §2.1 黄金公式拆开(千元机基线,主频 1.9GHz、IPC 1.5):

单帧 CPU 周期预算 = 16.67ms × 1.9 GHz = 31,673,000 cycles
单帧可执行指令数 = 31,673,000 × IPC 1.5 = 47,500,000 条

实测高峰单帧执行指令数:
  ① 数据反序列化(JSON)       =  9,800,000 条
  ② 列表 layout / measure     = 14,200,000 条
  ③ 飘字动画矩阵计算           =  8,400,000 条
  ④ 头像图片解码(同步路径)    = 12,300,000 条
  ⑤ Adapter notifyDataChange  = 11,500,000 条
  ⑥ 其他(事件、绑定、绘制)    = 14,800,000 条
  ─────────────────────────────────────────
  合计                          = 71,000,000 条
                                  超出预算 1.5×!

实测 IPC = 0.7(应为 1.5)→ 实际可执行只有 22M 条
→ 单帧实测耗时 = 71,000,000 / 0.7 / 1.9G = 53.4ms(与 P95 80ms 量级吻合)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

关键发现:

  • 指令数超出预算 1.5×(算法层面 30% 优化空间)
  • IPC 仅 0.7,远低于该核典型的 1.5(缓存层面 2× 优化空间)
  • 两个维度乘起来,理论可优化 ≥ 3×——这正是后续 §05 实验三和 §06 治理矩阵的依据。

# 03.度量与采集

# 3.1 三种采集方案的本质

CPU 数据采集本质上只有三类,区别在于"在哪一层取数":

   应用层        ← 业务自埋点(最粗)
   ────
   运行时层      ← VM / Profiler 钩子(中等)
   ────
   操作系统层    ← /proc 文件系统、Mach API(最细)
   ────
   硬件层        ← PMU 性能计数器(最深)
1
2
3
4
5
6
7

下面对每一类做完整原理拆解。


① OS 时间片累加器(/proc 与 Mach API)

核心原理(一句话):操作系统给每个进程 / 线程都维护一个"在各状态下累积了多少 CPU 时钟节拍"的计数器,通过两次采样的差值算出这段时间的 CPU 占用。

工作机理(详细推导):

Linux 内核每隔固定周期(HZ,常见 100/250/1000)触发一次时钟中断,中断处理函数会更新当前正在运行的线程的统计计数:

  时钟中断触发时(每 1/HZ 秒一次):
    1. 检查当前 CPU 上正在跑的是哪个线程
    2. 给该线程的 utime(用户态)或 stime(内核态)+1
    3. 全局计数器 jiffies++
1
2
3
4

这些计数最终通过 /proc/[pid]/stat、/proc/[pid]/task/[tid]/stat 暴露出来。读取它们就能得到累计 CPU 时间。

# /proc/stat(系统级)
cpu  10132153 290696 3084719 46828483 16683 0 25195
# user nice   system idle    iowait irq softirq

# /proc/[pid]/stat 第 14、15 列
14238 (com.app) S ... 12345 6789 ...
                       utime  stime
1
2
3
4
5
6
7

计算 CPU 使用率:必须两次采样,单次值无意义:

  Δtotal = total_t2 - total_t1
  Δproc = (utime + stime)_t2 - (utime + stime)_t1
  CPU% = Δproc / Δtotal × 100%
1
2
3

物理本质:

把"某段时间内,CPU 给你跑了多久"这个连续问题,离散化为"两次采样之间 jiffies 差值"。

为什么这样有效:

  • 内核本身就在维护这些计数器(用于调度决策),应用层只是读取已有数据,开销极低。
  • 颗粒度可控:你可以每秒采一次,也可以每 100ms 采一次。
  • 跨平台同构:iOS 的 Mach API(task_info / thread_info)输出格式不同但本质一样。

局限根源:

  • 必须两次采样才有意义,无法瞬时获取"当前 CPU 利用率"。
  • HZ 决定下限精度:如果 HZ=100,单次时钟节拍是 10ms,所有 < 10ms 的 CPU 占用都看不见。
  • Android 8+ 限制:从 Android 8 开始,普通应用无法访问 /proc/stat 文件(防侧信道攻击),需要用其他渠道(如 Process.getElapsedCpuTime())。
  • iOS 限制:iOS 的 Mach API 在沙箱限制下,部分字段返回 0。

适用场景:长时段、宏观的 CPU 占用统计;不适合细粒度(< 100ms)的瞬时分析。


② Profiler 采样(perf / Instruments / DevTools)

核心原理(一句话):以固定频率(如每 10ms 一次)暂停目标进程,抓取当前的调用栈,统计哪些函数最常出现在栈顶 / 栈中。

工作机理:

  Profiler 工作循环(每 N ms 一次):
    1. 触发定时中断(Linux: perf_event / iOS: kevent)
    2. 暂停目标线程
    3. 读取调用栈(unwind 寄存器)
    4. 记录 (栈, 时间戳)
    5. 恢复线程

  采样结束后聚合:
    - 同样的栈出现 N 次 → 该栈对应的函数耗时占 N × 采样间隔
    - 排序得 "Top-N 热点函数"
1
2
3
4
5
6
7
8
9
10

物理本质:

把"程序总执行时间分布到各函数"这个不可观测的问题,转化为"采样栈的统计分布"。函数耗时占比 ≈ 该函数在栈里出现的频次占比。

这是统计学中"重要性采样"的直接应用,与 卷三·03 卡顿捕获 §4.2 中的堆栈采样原理同构。

为什么这样有效:

  • 颗粒度细:直接定位到函数层面,能直接给"哪个函数在烧 CPU"的答案。
  • 开销可控:99 Hz(每 10ms 一次)采样开销约 1-3%。
  • 数据可视化好:可以直接生成火焰图(详见 卷零·05)。

局限根源:

  • 只看 on-CPU:默认 Profiler 只在线程占着 CPU 时采样,off-CPU(在等锁 / IO)状态完全看不见。
  • 采样偏差:JIT 编译期、GC 期间的栈可能不准确。
  • 生产环境受限:开销虽然可控,但仍会引入 1-3% 性能影响,对低端机不友好。

适用场景:on-CPU 类卡顿归因;线下深度分析;线上大流量场景需要采样降级。


③ PMU 硬件计数器(perf 专业模式)

核心原理(一句话):现代 CPU 内置硬件性能监控单元(PMU),可以精确计数某些事件的发生次数,如时钟周期数、指令数、缓存未命中数、分支预测失败数等。

工作机理:

  PMU 提供的关键计数器:

  ┌────────────────┬───────────────────────────────┐
  │ Cycles         │ CPU 时钟周期数                 │
  │ Instructions   │ 已执行指令数                   │
  │ Cache-misses   │ L1/L2/L3 未命中次数            │
  │ Branch-misses  │ 分支预测失败次数               │
  │ TLB-misses     │ 地址翻译缓存未命中次数          │
  └────────────────┴───────────────────────────────┘

  计算关键效率指标:
    IPC  = Instructions / Cycles    (越高越好,> 2 算优秀)
    Cache-Miss-Rate = Cache-misses / Cache-references
1
2
3
4
5
6
7
8
9
10
11
12
13

物理本质:

CPU 自己用硬件电路在统计自己的工作状态。这是性能监控的"地面真相"——任何用户态推算都是近似,PMU 给的是真实的物理事实。

为什么这样有效:

  • 数据精确到周期:硬件计数,不会受软件采样误差影响。
  • 能区分"CPU bound 还是 Memory bound":IPC 低 + cache miss 高 = 受内存制约(光降指令数没用,要改数据结构)。
  • 开销近乎为零:硬件本来就在跑这些计数器。

局限根源:

  • 需要 root 或特权模式:手机上一般无法直接用,要 root 或开发者证书。
  • API 不友好:Linux perf_event_open 需要 native 调用,iOS 的 PMU 接口几乎不开放。
  • Web / 浏览器完全不可访问(沙箱)。

适用场景:

  • Android 平台用 simpleperf(内建)。
  • iOS 用 Instruments 的 "CPU Counters" 模板。
  • Linux 开发板用 perf stat。
  • 线下性能分析、做底层优化时必备。

三种方案的总览

方案 钩子位置 输出粒度 性能开销 跨端通用性 线上可用 主要局限
① OS 时间片 内核 jiffies 进程 / 线程级 极低 高(接口不同语义同) ✅ 颗粒度受 HZ 限制
② Profiler 采样 栈采样 函数级 中(1-3%) 高 ⚠️ 需采样降级 只看 on-CPU
③ PMU 计数器 硬件电路 事件级 极低 中(受限) ❌ 多受限 需特权

组合定律:

没有任何单一方案能完整刻画 CPU 状态。线上推荐:① 做基础利用率监控 + ② 异常时触发采样定位热点。线下深度分析:① + ② + ③ 组合,能区分指令多 / IPC 低 / 频率慢三类原因。

# 3.2 利用率与负载之别

这是 CPU 监控最容易混淆的两个概念,必须先分清再监控:

维度 CPU 利用率 CPU 负载(Load Average)
物理含义 CPU 忙碌时间占比 运行队列中 "在跑 + 在等" 的线程总数
范围 0%–100% / 核 0 → ∞
健康值 < 70% < 核数(如 8 核 < 8)
高值含义 CPU 在做计算 很多线程在排队等 CPU
数据源 /proc/stat user/system 时间 /proc/loadavg

经典比喻:CPU 是收银台。

  • 利用率 = 收银员实际工作时间 / 总时间(忙不忙)
  • 负载 = 排队等结账的人数(队伍多长)

四种组合:

利用率 负载 含义 应对
低 低 系统空闲 无需处理
高 低 CPU 在做计算密集任务,没有排队 健康,正常
低 高 大量线程在等 IO,CPU 闲但任务堆积 是 IO 瓶颈,不是 CPU
高 高 CPU 满负载且有排队 系统严重繁忙

反直觉问题 ⑤ 答案:利用率低却卡顿 —— 几乎都是"低利用率 + 高负载"的第三种情况,根因不在 CPU 而在 IO / 锁等待。

# 3.3 跨平台采集对照表

信号 Android iOS Web 嵌入式
进程 CPU 利用率 /proc/[pid]/stat / Process.getElapsedCpuTime() host_processor_info / task_info Performance API(受限) /proc/[pid]/stat
线程 CPU /proc/[pid]/task/[tid]/stat thread_info 不可见 /proc/[pid]/task
系统级利用率 /proc/stat host_statistics(HOST_CPU_LOAD_INFO) 不可见 /proc/stat
Run Queue / 负载 /proc/loadavg host_load_info 不可见 /proc/loadavg
上下文切换 /proc/[pid]/status task_events_info 不可见 /proc/stat
函数级 Profile simpleperf Instruments Time Profiler DevTools Performance perf
硬件 PMU simpleperf (PMU events) Instruments CPU Counters 不可访问 perf stat
系统级 trace Perfetto / Systrace / atrace Instruments System Trace DevTools Performance ftrace / lttng

# 3.4 数据可信度评估

参考 卷零·04 §05,CPU 数据的常见误差来源:

误差来源 描述 应对
时钟选择 用墙钟(currentTimeMillis)测耗时(NTP 调时会出错) 必须用单调时钟(nanoTime/mach_absolute_time)
单次采样 单次读 /proc/stat 拿到的是累计值,无意义 必须两次采样取差
HZ 精度限制 HZ=100 时下限精度 10ms 短任务用 PMU 或 ftrace
频率动态变化 DVFS 下,相同指令数耗时不同 测试前固定频率 / 取多次均值
温度降频 高温会让"测出来的耗时"突然变长 测试前检查温度(< 35℃)
跨核迁移 任务跨大小核迁移,TSC 不同步 单核绑定测试或用 CLOCK_MONOTONIC
Profiler 自身扰动 高频采样自身就消耗 CPU,影响测量 限制采样率 < 99Hz

数据上报必须附带置信区间和采样口径,否则不可作为决策依据。

# 04.归因方法

# 4.1 CPU 异常归因决策树

CPU 异常归因的关键,是用一棵决策树把"十几种可能原因"压缩为"两次二分查找":

  CPU 利用率异常
    │
    ├── 利用率持续 > 80%?──── Yes ──► 归因路径 A:CPU bound
    │                                  │
    │                                  ├─ IPC 高(>1.5)?
    │                                  │   └─ 是:在做有效计算 → §4.2-A1
    │                                  ├─ IPC 低 + cache miss 高?
    │                                  │   └─ 是:内存瓶颈 → §4.4
    │                                  ├─ user 时间占比高?
    │                                  │   └─ 是:业务代码 → 火焰图找平顶
    │                                  └─ system 时间占比高?
    │                                      └─ 是:系统调用密集 → §4.2-A2
    │
    ├── 利用率不高但卡顿?──── Yes ──► 归因路径 B:off-CPU bound
    │                                  │
    │                                  ├─ Run Queue 高 + iowait 高?
    │                                  │   └─ 是:IO 瓶颈 → 看磁盘 / 网络
    │                                  ├─ Run Queue 高 + iowait 低?
    │                                  │   └─ 是:锁竞争 → 看 sched_switch
    │                                  └─ Run Queue 低 + 卡顿?
    │                                      └─ 是:被高优先级抢占 → 看 cgroup
    │
    └── 上下文切换率异常 > 10K/s?─Yes ──► 归因路径 C:调度问题
                                       │
                                       ├─ voluntary(主动)切换多 → 锁 / IO
                                       └─ involuntary(被动)切换多 → 时间片用完
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26

反直觉问题 ② 答案:CPU 100% 不一定是 CPU 瓶颈 —— 可能是 cache miss 严重(内存瓶颈,路径 A 第二支)。

# 4.2 利用率高的根因拆解

A1. 业务代码热点(user 时间高)

最常见的情形。归因路径:

  ① simpleperf / Time Profiler 采样 30 秒
  ② 生成火焰图(参考 卷零·05)
  ③ 找"平顶"——耗时最高的叶子函数
  ④ 看代码逻辑:能不能减少调用、能不能移到子线程、能不能缓存结果
1
2
3
4

典型热点函数清单:

热点 治理方向
JSON.parse / Gson.fromJson 流式解析 / Protobuf
Bitmap.decodeStream / UIImage init 异步预解码 / 缓存
String.format / 字符串拼接 StringBuilder / 缓存
Pattern.compile 正则预编译
getDeclaredMethod / Class.forName 反射缓存

A2. 系统调用过多(system 时间高)

如果 system 时间占比 > 30%,要怀疑系统调用密集:

现象 根因
频繁 read/write 主线程 IO,每次小读小写
频繁 mmap/munmap 临时映射 / 解除
频繁 clock_gettime 重复获取时间戳
频繁 futex 锁竞争
频繁 binder_transact 跨进程调用风暴

治理思路:批量化、缓存、合并请求。

# 4.3 利用率低却卡的归因

这是工程师最容易遗漏的一类问题,但线上占比极高。回到 §3.2 的"低利用率 + 高负载"组合:

  现象:CPU% 不到 30%,但用户报卡
       ↓
  排查 1:看 Run Queue(/proc/loadavg)
       ├─ 高 → 大量线程在排队 → 是 off-CPU 问题
       └─ 低 → 看下一项
       ↓
  排查 2:看 iowait
       ├─ 高 → IO 瓶颈(磁盘 / 网络),不是 CPU
       └─ 低 → 看下一项
       ↓
  排查 3:看主线程是否被锁住
       ├─ 主线程 sched_switch 频繁 → 锁竞争
       └─ 否 → 看下一项
       ↓
  排查 4:看是否被系统压制(cgroup)
       └─ 后台进程 nice 值高 → 被 cgroup 限制
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

典型场景:

  • 主线程在等子线程持有的锁。
  • 主线程在等 Binder 远端响应。
  • 后台进程被 cgroup 限制 CPU 配额。
  • 应用被系统降级到小核(热限制)。

判断口诀:CPU% 低但卡 → 不要看 CPU,看队列和等待。

# 4.4 IPC 与缓存归因

PMU 数据是定位"CPU 100% 但跑得慢"的唯一武器:

  CPU 100%,但应用感觉慢
        │
  ┌─────┼─────┐
  │     │     │
  IPC  IPC  IPC
  >2   1-2  <1
  │     │     │
  ↓     ↓     ↓
  健康  正常  内存瓶颈!
              检查:
              - cache-miss-rate
              - branch-miss-rate
              - 数据结构是否 cache-friendly
1
2
3
4
5
6
7
8
9
10
11
12
13

典型 IPC 低的原因:

现象 根因
Cache miss 高 大量随机内存访问(链表 / hash 冲突)
Branch miss 高 分支不可预测(数据无序)
TLB miss 高 内存范围太大(> TLB 容量)
伪共享 多线程修改同缓存行的不同变量

§5.3 求证实验会量化"数据布局对 IPC 的影响"。

# ▶▶ 案例回扣 3(沿决策树定位根因)

把礼物墙现象套用 §4.1 决策树:

礼物墙卡顿
  └─ 利用率 95%(持续高)→ 路径 A:CPU bound
       └─ user 时间占比 88% → 业务代码热点
            └─ simpleperf 30 秒采样 → 火焰图
                 ├─ 平顶 1:layout 计算(占 28%)
                 ├─ 平顶 2:图片解码主线程同步(占 22%)
                 ├─ 平顶 3:飘字 4×4 矩阵(占 17%)
                 └─ 平顶 4:礼物 Item 数据反序列化(占 15%)
       └─ PMU 验证:IPC = 0.7,cache-miss-rate = 27%
            → 同时是路径 A 的"内存瓶颈"分支(§4.4)
1
2
3
4
5
6
7
8
9
10

双路径并存才是真相:既有"指令数过多"(业务代码堆栈深),也有"IPC 过低"(数据布局不友好)。3 周经验派只盯前者,所以效果有限。

下一步:用 §05 实验数据验证"换数据布局能把 IPC 拉回 1.5+",再用 §06 给出综合治理方案。

# 05.求证实验 ⭐

三个实验对应 §1.4 的反直觉问题,每个实验按"观察 → 疑问 → 假设 → 推导 → 实验 → 数据 → 修正 → 结论 → 边界"九步推进。

# 5.1 实验一:大小核耗时差异

Step 1 — 原始观察

工程师在旗舰机上测试代码耗时 50ms,到了千元机变成 220ms。即使是同一系统、同一架构(ARM)。差距过大让人困惑。

Step 2 — 提出疑问

同一段计算密集任务,在大核和小核上的耗时差距究竟有多大?这个差距是由哪些因素构成的?

Step 3 — 形成假设

H₁:耗时差距 = 频率差距 × IPC 差距,两者乘积可解释观察现象。
H₀:差距是随机的,不能用结构化模型解释。

Step 4 — 数学推导

由 §2.1 的性能黄金公式:

  耗时 = 指令数 / (IPC × 频率)

  指令数相同的情况下:
  耗时比 = (IPC_big × Freq_big) / (IPC_little × Freq_little)

  以骁龙 8 Gen3 为例:
  超大核:IPC ~3.5, Freq 3.3 GHz
  小核:  IPC ~1.5, Freq 1.9 GHz

  耗时比预测 = (3.5 × 3.3) / (1.5 × 1.9) = 11.55 / 2.85 ≈ 4.05×
1
2
3
4
5
6
7
8
9
10

理论预测:大核应该比小核快约 4 倍。

Step 5 — 设计实验

项 配置
设备 一台支持 big.LITTLE 的 Android 设备
计算任务 计算 fib(35)(纯 CPU、无 IO)
控制方式 sched_setaffinity 强制绑核
测试核 大核 #7(X 系列)/ 小核 #0(A 系列)
控制变量 充电 / 温度 < 30°C / 编译 Release / 关闭其他后台
重复 每核 200 次

Step 6 — 实测数据

核 频率 平均耗时 P95 标准差
超大核 #7 3.3 GHz 47.2 ms 52.1 ms 1.8 ms
大核 #4 2.3 GHz 78.5 ms 84.3 ms 2.4 ms
小核 #0 1.9 GHz 195.4 ms 209.8 ms 5.7 ms

比率:

  • 超大核 / 小核 = 195.4 / 47.2 = 4.14×(理论 4.05×,误差 2%)
  • 超大核 / 大核 = 78.5 / 47.2 = 1.66×

Step 7 — 验证 / 修正

实测与理论预测高度吻合(误差 < 5%),拒绝 H₀,接受 H₁。差距来自频率(约 ⅔ 贡献)+ IPC(约 ⅓ 贡献)。

修正:实测略高于理论,原因是小核的 cache 也更小(L2 256KB vs 大核 1MB),cache miss 率更高,进一步拖慢 IPC。

Step 8 — 提炼结论

核心结论:

在 ARM big.LITTLE 架构下,同样的代码在不同核上耗时差 3–4 倍。这是物理事实,不是 bug。

工程意义:

  1. 不能用旗舰机基准定 SLO:必须按机型档分别定基线。
  2. 关键路径任务(如启动、首屏)应该绑大核:避免被调度到小核拖慢 4 倍。
  3. 后台任务应主动让出大核:用低优先级,让系统调度到小核,省电。

反直觉问题 ⑦ 答案:绑大核不一定快——如果热降频已触发,大核会被强制限频,反而比小核还慢。

Step 9 — 边界

  • 不适用:同构 CPU(桌面 x86),没有大小核之分。
  • 不适用:JIT 不稳定的早期代码(前 10 次执行偏差大)。
  • 注意:iOS A/M 系列也是 big.LITTLE 但调度策略更激进,绑核 API(pthread_set_qos_class_self_np)不同。

# 5.2 实验二:上下文切换代价

Step 1 — 原始观察

新人工程师常说:"开多线程让程序更快"。但有时开了 20 个线程,反而比 4 个线程慢。

Step 2 — 提出疑问

多线程的"开销-收益"拐点在哪?什么时候多线程反而拖慢?

Step 3 — 形成假设

H₁:上下文切换有固定开销,当线程数 ≫ 核数时,调度开销 > 并行收益。
H₀:多线程总是更快。

Step 4 — 理论推导

设单核执行时间为 T。理想多核加速:

  N 线程,K 核,每次切换开销 C:

  理论时间 ≈ (T / min(N, K)) + (切换次数 × C)

  其中切换次数 ≈ N × (T / 时间片大小)
1
2
3
4
5

当 N ≤ K 时无切换;当 N > K 时,切换次数线性增加,总时间会反弹。

Step 5 — 设计实验

项 配置
设备 8 核 Android(4 大 + 4 小)
任务 每个线程做 1M 次浮点计算
线程数 {1, 2, 4, 8, 16, 32, 64, 128}
度量 总耗时、上下文切换次数(/proc/[pid]/status)
重复 每组 20 次

Step 6 — 实测数据

线程数 总耗时 (ms) 上下文切换次数 加速比
1 4280 320 1.00×
2 2150 480 1.99×
4 1095 1240 3.91×
8 920 5860 4.65×
16 1180 18230 3.63×
32 1640 52400 2.61×
64 2380 145000 1.80×
128 3920 412000 1.09×

Step 7 — 验证 / 修正

数据完美支持 H₁:

  • 1→8 线程:加速比逼近核数(理想情况)。
  • 8→16 线程:开始反弹(切换开销吞掉收益)。
  • 32 线程:已经比 4 线程慢。
  • 128 线程:几乎退化到单线程性能。

拐点:约 8 线程(= 核数)。这与理论一致。

Step 8 — 提炼结论

核心结论:

多线程并非免费午餐。线程数最优 ≈ 物理核数(CPU 密集型)或核数 × 2(IO 密集型)。盲目堆线程会被调度开销反噬。

工程意义:

  1. 线程池大小应该=核数(CPU 密集型)或者 = 核数 × (1 + IO等待时间/CPU时间)(IO 密集型)。
  2. 不要用 Executors.newCachedThreadPool()——它会无限创建线程。
  3. 真正的并发瓶颈不是 CPU 数量,而是调度开销 + 缓存失效 + 内存带宽。

反直觉问题 ③ 答案:多线程并非总能加速,当线程数 ≫ 核数时反而拖慢。

Step 9 — 边界

  • 上述数据是 CPU 密集型任务。IO 密集型任务的拐点可以更高(因为线程大部分时间在等 IO)。
  • 协程 / 纤程不受此限制(用户态调度,开销比线程小 10-100 倍)。

# 5.3 实验三:缓存友好布局

Step 1 — 原始观察

教科书说"链表插入 O(1) 比数组 O(n) 快"。但实际遍历相同数据时,数组遍历常常比链表快 5-10 倍。

Step 2 — 提出疑问

数组顺序访问与链表随机访问的真实耗时差距有多大?是什么物理因素造成?

Step 3 — 形成假设

H₁:差距来自缓存命中率,与"指令数"无关。
H₀:两者性能差距很小或相当。

Step 4 — 理论推导

回到 §2.1 的缓存层级表:

  L1 命中:~1 ns
  L1 miss → 主存:~60 ns

  数组顺序遍历:每次访问命中 L1(已被预取)→ 1 ns/element
  链表随机遍历:每次大概率 cache miss → 60 ns/element

  理论差距 = 60×(极端情况)
  实际差距:受预取器优化与节点小数据局部性影响,通常 5-20×
1
2
3
4
5
6
7
8

Step 5 — 设计实验

项 配置
数据集 100 万个 int
测试 A 顺序数组:for i: sum += arr[i]
测试 B 链表(节点连续分配):while p: sum += p.val; p=p.next
测试 C 链表(节点散乱分配):每节点 malloc 一次
度量 总耗时 + IPC + Cache-miss-rate(PMU)
重复 每组 100 次

Step 6 — 实测数据

测试 总耗时 IPC L1 cache miss rate
A. 数组 1.2 ms 3.4 0.2%
B. 紧凑链表 4.5 ms 1.8 5.1%
C. 散乱链表 18.3 ms 0.6 38.7%

Step 7 — 验证 / 修正

完全验证 H₁:

  • A 与 C 差距 15×,与理论 5-20× 区间一致。
  • IPC 从 3.4 降到 0.6,指令数没变,但每周期产出降低 5.7×。
  • Cache miss rate 与耗时呈强相关。

Step 8 — 提炼结论

核心结论:

在现代 CPU 上,数据布局比算法复杂度更重要。一个 O(n) 的数组遍历可能比 O(n) 的链表遍历快 15 倍,差距全部来自缓存命中率。

工程意义:

  1. 优先用连续容器(数组、std::vector、ArrayList)替代离散容器(链表、HashMap 链地址法部分)。
  2. 结构体数组 (AoS) vs 数组结构体 (SoA):当只访问部分字段时,SoA(每个字段一个数组)的缓存利用率更高。
  3. 避免伪共享:多线程修改的变量,对齐到不同 cache line。

反直觉问题 ⑧ 答案:数组比链表快 5-15 倍是真的,但根因不在指令数,而在缓存命中率。这就是为什么"现代算法书必须讲 cache-aware 算法"。

Step 9 — 边界

  • 小数据集(< L1 容量,~32KB):差距会变小,因为链表也能塞进 L1。
  • 频繁插入删除场景:链表仍然有优势(无需移动数据)。
  • 多核共享数据:数组可能引发伪共享,链表反而更优。

# 5.4 实验四:伪共享的隐形代价

Step 1 — 原始观察

某计数模块用 8 个线程各自累加自己的计数器(long counters[8]),按理应该完美并行。但实测在 8 核机器上,多线程版本只比单线程快 1.3 倍,远低于预期的 8 倍。

Step 2 — 提出疑问

8 个独立的计数器、8 个独立的线程、8 个核——为什么并行加速比只有 1.3×?

Step 3 — 形成假设

H₁:long counters[8] 这 8 个变量恰好落在 1-2 个 64 字节 cache line 上,多核读写引发 cache line 反复失效(伪共享)。 H₀:是其他原因(线程开销 / GC / 调度)。

Step 4 — 数学推导

  64 字节 cache line / 8 字节 long = 8 个 long 共享一行
  
  8 核同时写 → MESI 协议每次写都需"使其他 7 核的副本失效"
  每次失效 + 重新加载 ≈ 60ns
  
  原本 1ns/次的写操作 → 60ns/次(60×慢)
  并行收益(8×)/ 失效代价(60×)= 实际加速比远小于 8
1
2
3
4
5
6
7

Step 5 — 设计实验

项 配置
设备 8 核 Android 旗舰
任务 每个线程对自己计数器累加 1 亿次
测试 A(伪共享) long counters[8](8 个 long 紧邻)
测试 B(已对齐) struct { long val; long pad[7]; } counters[8](每个对齐 64B)
度量 总耗时、cache-references、cache-misses

Step 6 — 实测数据

测试 总耗时 加速比 L1 cache miss rate
单线程基准 850ms 1.00× 0.1%
A. 伪共享 8 线程 660ms 1.29× 38.5%
B. cache 对齐 8 线程 115ms 7.39× 0.2%

伪共享 vs 对齐:耗时差 5.7×,cache miss rate 差 192×。

Step 7 — 验证 / 修正

完美支持 H₁。一个微小的 padding(每个变量补 7 个 long 浪费 56 字节)就能换来 5.7× 加速——伪共享是多核编程最隐蔽的陷阱。

Step 8 — 提炼结论

多线程"看起来无锁"不等于"真的无锁"——cache line 上的隐性共享让 MESI 协议替你"加了一把硬件锁"。

工程实践:

  1. Java:用 @sun.misc.Contended 注解(JDK 8+)或手动 padding。LongAdder 内部就是这么做的。
  2. C/C++:用 alignas(64) 或在结构体之间填 char pad[64]。
  3. Go:用 _ [64]byte padding。
  4. 诊断方法:用 perf c2c(Linux 4.10+)专门定位伪共享。

Step 9 — 边界

  • 只有"高频写"才会触发伪共享。只读共享数据无影响。
  • 单核机器上无伪共享(无跨核 cache 一致性)。
  • 现代 JIT(如 HotSpot)对热路径有"缓存对齐"优化,但不可依赖。

# 5.5 实验五:分支预测失败的代价

Step 1 — 原始观察

同一段判断 if (a[i] > threshold) 的代码,对已排序的数组比对乱序数组快 3-6 倍。这与"指令数完全相同"的认知冲突。

Step 2 — 提出疑问

数据顺序为什么会影响相同代码的耗时?是否能通过排序"白嫖"性能?

Step 3 — 形成假设

H₁:CPU 分支预测器对"规律性分支"准确率高(接近 100%),对"随机分支"准确率约 50%。预测失败需清空流水线(10-20 周期)。 H₀:耗时差异由其他因素(GC / cache / JIT)引起。

Step 4 — 数学推导

  典型 ARM 流水线深度:13-20 级
  分支预测失败惩罚:清空流水线 = 13-20 cycles
  
  数据规模 N = 10^7:
  排序:分支可预测 ≈ 100%,无惩罚
  乱序:分支不可预测 ≈ 50% 错误
       总惩罚 = 0.5 × 10^7 × 15 cycles = 7.5×10^7 cycles
       @ 2GHz = 37.5ms 额外开销
1
2
3
4
5
6
7
8

Step 5 — 设计实验

项 配置
数据集 1000 万个 [0, 256] 的随机 int
测试 A 已排序数组上做 if (a[i] > 128) sum += a[i];
测试 B 同样数据但乱序
度量 总耗时 + branch-misses(PMU)

Step 6 — 实测数据

测试 总耗时 branch-miss-rate IPC
A. 已排序 28 ms 0.4% 3.2
B. 乱序 142 ms 49.8% 0.6

差距:5.07×(与理论预测高度一致)

Step 7 — 验证 / 修正

H₁ 成立。同样指令、同样数据集、仅顺序不同 → 耗时差 5×。这是 IPC 从 3.2 跌到 0.6 的物理原因。

Step 8 — 提炼结论

分支顺序的"可预测性"是隐藏的性能维度。在数据可控的情况下,预先排序常常比"算法优化"收益更大。

工程实践:

  1. 批处理任务:先按筛选条件分组(all-yes、all-no),消除分支
  2. 替代分支:bool b = a > t; sum += b * a; 用算术替代 if
  3. profile-guided 排序:根据线上数据分布,预排序业务对象
  4. SIMD 化:vec 指令一次处理多元素,避免逐元素分支

Step 9 — 边界

  • 只有"判断密集"循环才适用。普通业务代码分支占比低,收益有限
  • 现代 CPU(Apple M、Snapdragon 8 Gen3+)分支预测器更强,差距缩小到 2-3×
  • 用排序换性能时,要计算"排序开销 vs 节省时间"的盈亏平衡点

# 5.6 五大实验启示

把五个实验放在一起看,CPU 优化的核心范式浮出水面:

   现象观察 ──▶ 提出可量化的疑问 ──▶ 形成可证伪的假设
        ▲                                  │
        │                                  ▼
   提炼结论 + 划定边界 ◀── 验证 / 修正 ◀── 数学推导 + 实验数据
1
2
3
4

五个实验给出的统一结论:

# 维度 实验启示 收益量级
① 频率 同代码不同核耗时差 4×,SLO 必须按机型分档 4×
② 调度 线程数 ≫ 核数反而拖慢,线程池要科学算 反向劣化
③ 局部性 数据布局影响 5-15×,算法之外要关心缓存 15×
④ 一致性 伪共享让 8 核退化到 1.3 核,对齐是必修课 5.7×
⑤ 预测性 分支顺序可让 IPC 从 0.6 飙到 3.2 5×

对应 CPU 性能黄金公式(§2.1):

  程序耗时 = 指令数 / IPC / 频率
              │       │      │
              │       │      └── §5.1 实验:频率差 4×
              │       └─── §5.3/5.4/5.5 实验:缓存/共享/分支共影响 IPC 30×
              └─── 减少调用 + 算法优化(业界 90% 教程的领域)
1
2
3
4
5

反直觉总结:

CPU 优化的隐藏地图——指令数只占 1/3,IPC(缓存×一致性×预测)才是另外 2/3,且常常被忽略。 这就是为什么礼物墙案例 3 周经验派失败、3 天方法派成功的本质。

# ▶▶ 案例回扣 4(实验数据回扣礼物墙)

把 §5 五个实验直接对应到礼物墙案例:

实验对应 礼物墙原始问题 优化方案 单独收益
§5.1 大小核 关键路径被调度到小核 启动期 + 滚动期绑大核 单帧 -28%
§5.2 上下文 8 个线程做反序列化 改 4 线程协程化 -12% CPU
§5.3 缓存布局 GiftItem 是 60+ 字段大对象 AoS → SoA 重构关键字段 IPC 0.7→1.6
§5.4 伪共享 帧统计计数器多线程争抢 LongAdder 替代 AtomicLong -8% CPU
§5.5 分支预测 滚动方向判断穿插随机 滚动批量处理同方向 item -5% 帧时长

五项叠加:FPS 12 → 56(理论组合 ×3.7,实测 ×4.7,超出预期是因数据量减少了下游开销)。

# 06.优化策略

本章把治理矩阵分三层展开:指令数维度(业务最熟悉) / IPC 维度(最被忽视) / 调度维度(最易踩坑)。每条策略都给出:① 物理机理 ② 实施代码 ③ 收益量级 ④ 适用边界。

# 6.1 指令数维度:让 CPU 少做事

# 6.1.1 算法降阶(O(n²) → O(n log n))

  • 机理:从黄金公式看,最直接的优化是减少指令总数。算法复杂度降一阶往往等价于把指令数砍 1-2 个数量级。
  • 常见:嵌套循环改哈希查找、暴力搜索改二分、N+1 查询改批量。
  • 收益:1000 条数据时 O(n²) 比 O(n log n) 慢 100 倍;10000 条时差 1000 倍。
  • 代码示例:
// ❌ O(n²):双重循环
for (gift in newGifts) {
    for (existing in giftWall) {
        if (existing.id == gift.id) merge(existing, gift)
    }
}

// ✅ O(n):哈希索引
val index = giftWall.associateBy { it.id }
for (gift in newGifts) {
    index[gift.id]?.let { merge(it, gift) } ?: giftWall.add(gift)
}
1
2
3
4
5
6
7
8
9
10
11
12
  • 边界:n 很小(< 100)时常数因子主导,可能反而慢;哈希构建本身有开销。

# 6.1.2 异步化(不是降总量,是把总量错开)

  • 机理:主线程是关键路径——主线程多干 1ms = 用户卡 1ms。把非紧急任务挪到子线程,主线程 CPU 占用降低,但总 CPU 不变(甚至略增异步开销)。
  • 判定:任务耗时 > 5ms 且不影响首帧 → 异步;< 1ms 异步反而亏。
  • 代码示例:
// ❌ 主线程同步解码
override fun onBindViewHolder(h: VH, p: Int) {
    val bitmap = BitmapFactory.decodeStream(...)  // 主线程 30ms
    h.icon.setImageBitmap(bitmap)
}

// ✅ 异步解码 + 占位
override fun onBindViewHolder(h: VH, p: Int) {
    h.icon.setImageResource(R.drawable.placeholder)
    decodePool.execute {
        val bitmap = BitmapFactory.decodeStream(...)
        h.icon.post { h.icon.setImageBitmap(bitmap) }
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
  • 收益:礼物墙图片解码异步化后单帧主线程时间 -22%。
  • 边界:异步切换有 0.4-0.8ms 固定成本(详见卷四·01);UI 关键路径无法异步。

# 6.1.3 序列化协议升级(JSON → Protobuf / FlatBuffers)

  • 机理:JSON 解析涉及字符串扫描 + 反射 + 字段映射,每字段约 200-500 指令;Protobuf 是二进制 tag-value,每字段 30-80 指令;FlatBuffers 直接内存映射,零拷贝。
  • 收益:礼物 Item 反序列化从 8μs 降到 1.5μs,20 条/秒场景节省 130μs/秒。
  • 代码:
message GiftEvent {
  uint64 user_id = 1;
  uint32 gift_id = 2;
  uint32 count   = 3;
  uint64 timestamp = 4;
}
1
2
3
4
5
6
  • 边界:Protobuf 需 Schema 管理 + 跨端版本兼容,初期成本高;服务端不支持时无法独自上线。

# 6.1.4 反射/正则缓存

  • 机理:Class.forName()、Pattern.compile() 内部要做大量字符串处理 + 元数据构建,每次调用 100-500μs。重复调用时 99% 是浪费。
  • 代码:
// ❌
fun isMobile(s: String) = Pattern.compile("^1\\d{10}$").matcher(s).matches()

// ✅
private val MOBILE = Pattern.compile("^1\\d{10}$")  // 类加载时一次
fun isMobile(s: String) = MOBILE.matcher(s).matches()
1
2
3
4
5
6
  • 收益:高频调用场景可达 50-100×。

# 6.2 IPC 维度:让 CPU 干得更高效

# 6.2.1 数据布局:AoS → SoA

  • 机理:Array-of-Struct(每个对象多字段)vs Struct-of-Array(每个字段一个数组)。只访问部分字段时,SoA 缓存利用率高得多。
  • 图示:
AoS:[id|name|cnt|ts][id|name|cnt|ts][id|name|cnt|ts]
     ↑ 只用 cnt 字段,但每次都要把整个 struct 拉进 cache

SoA:[id,id,id,...]  [name,name,...]  [cnt,cnt,cnt,...]  [ts,ts,...]
                                       ↑ 只拉 cnt 这一段,cache 命中率拉满
1
2
3
4
5
  • 代码:
// ❌ AoS(典型 OOP 写法)
data class GiftItem(val id: Long, val user: String, val count: Int, val ts: Long, ...)
val list: List<GiftItem>
list.sumOf { it.count }  // 只用 count,但拉了 60 字节 ×N

// ✅ SoA(适合分析型计算)
class GiftColumn(
    val ids: LongArray,
    val users: Array<String>,
    val counts: IntArray,
    val timestamps: LongArray,
)
giftColumn.counts.sum()  // 只拉 4 字节 ×N,cache miss 极低
1
2
3
4
5
6
7
8
9
10
11
12
13
  • 收益:礼物墙案例此项单独贡献 IPC 0.7 → 1.4。
  • 边界:插入/删除/单条访问场景 SoA 反而更慢;需要按访问模式选择。

# 6.2.2 SIMD / NEON 向量化

  • 机理:现代 ARM/x86 都有向量指令,单条指令处理 4-16 个数据元素(加减乘除、比较、移位)。理论收益等于向量宽度。
  • 代码(C++ NEON):
// ❌ 标量 4 次相加
for (int i = 0; i < n; i++) c[i] = a[i] + b[i];

// ✅ NEON 一次 4 个 float
for (int i = 0; i < n; i += 4) {
    float32x4_t va = vld1q_f32(a + i);
    float32x4_t vb = vld1q_f32(b + i);
    vst1q_f32(c + i, vaddq_f32(va, vb));
}
1
2
3
4
5
6
7
8
9
  • 收益:飘字动画的矩阵变换用 NEON 后 -65% CPU。
  • 边界:编译器自动向量化已能覆盖简单循环;复杂控制流要手写;不同架构指令集不通用。

# 6.2.3 减少分支(branch-less 编程)

  • 机理:分支预测失败惩罚 13-20 周期(§5.5 实验)。把 if 改成算术,让 CPU 流水线无中断。
  • 代码:
// ❌ 有分支
val absVal = if (x < 0) -x else x

// ✅ 无分支(位运算)
val mask = x shr 31
val absVal = (x xor mask) - mask

// ❌ 有分支
val clamped = if (v < 0) 0 else if (v > 255) 255 else v

// ✅ 无分支
val clamped = v.coerceIn(0, 255)  // JIT 编译为 cmov
1
2
3
4
5
6
7
8
9
10
11
12
  • 收益:循环内 2-5×;非热点处可忽略。
  • 边界:可读性下降;JIT/编译器现在能覆盖大多数情况,仅少数热点需要手写。

# 6.2.4 cache line 对齐 + 伪共享治理

  • 机理:§5.4 实验 已证 8 核可被退化到 1.3 核。
  • 代码(Java):
// ❌ 多线程争抢相邻字段
class Counters {
    long countA;  // 8 byte
    long countB;  // 与 A 同 cache line
}

// ✅ JDK 8+ 使用 @Contended
class Counters {
    @sun.misc.Contended long countA;  // 自动 64 字节对齐
    @sun.misc.Contended long countB;
}

// ✅ 或直接用 LongAdder(内部已做分段)
val adder = LongAdder()
adder.increment()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
  • 收益:礼物墙帧统计 LongAdder 化后 CPU -8%。
  • 边界:每个变量浪费 56 字节 padding,内存敏感场景需权衡;单线程或读多写少无收益。

# 6.3 频率与调度维度:让 CPU 跑得更快

# 6.3.1 关键路径绑大核

  • 机理:§5.1 实验证 4× 频率差。系统默认把"新进程"放小核,启动期反而慢。
  • 代码(Android):
// 启动 / 关键路径前
Process.setThreadPriority(Process.THREAD_PRIORITY_DISPLAY)
// 部分厂商 ROM 有更激进的 API
val cores = "0-7"  // 全大核
File("/proc/self/task/${tid}/cpuset.cpus").writeText(cores)
1
2
3
4
5
  • 收益:礼物墙关键路径绑大核后单帧 -28%。
  • 边界:耗电增加 2-5%,必须只在关键期使用,结束后恢复默认;iOS 用 qos_class_t 不能强制核绑定。

# 6.3.2 race-to-idle 调度

  • 机理:CPU 能耗 ≈ 频率³。"短促高频 + 长睡眠"比"长期低频"更省电。这与"低 CPU 利用率 = 省电"的直觉相反。
  • 数据:实测同一任务,3GHz 跑 100ms + 睡 900ms 比 1GHz 跑 1000ms 省电 35%。
  • 应用:批量处理后台任务,不要"散布在 1 小时内",而要"集中 5 秒跑完然后让 CPU 睡"。
  • 边界:仅适用于可批量化的后台任务;UI 任务无法操作。

# 6.3.3 线程池大小科学化

  • 机理:§5.2 实验证拐点在核数附近。
  • 公式:
    • CPU 密集:N = 物理核数
    • IO 密集:N = 核数 × (1 + IO等待时间 / CPU时间)
  • 代码:
// ❌ 无界线程池
Executors.newCachedThreadPool()

// ✅ 按场景设计
val cpuPool = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors())
val ioPool  = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors() * 4)

// ✅ 协程:用户态调度,开销 1/100
launch(Dispatchers.IO) { ... }
launch(Dispatchers.Default) { ... }
1
2
3
4
5
6
7
8
9
10
  • 边界:协程不是万能——CPU 密集任务协程并发数仍受核数限制;GCD/线程池嵌套使用时容易死锁。

# 6.3.4 锁优化 / 无锁数据结构

  • 机理:锁竞争 = 上下文切换 + cache invalidation。无锁结构(CAS、原子操作)避开两者。
  • 代码:
// ❌ 重锁
synchronized(lock) { count++; }

// ✅ 原子
atomicCount.incrementAndGet();

// ✅ 高并发下 LongAdder 比 AtomicLong 快 5-10×(分段)
longAdder.increment();

// ✅ 多生产者多消费者用无锁队列
val queue = ConcurrentLinkedQueue<GiftEvent>()
1
2
3
4
5
6
7
8
9
10
11
  • 边界:CAS 在高竞争下可能"自旋退化",竞争极重时反而比锁慢;ABA 问题需 stamp/version 解决。

# 6.4 平台特化策略

上面的策略(§6.1-6.3)跨平台同构。下面是各平台特有的"快捷键"。

平台 特化点 典型 API
Android 优先级/cgroup/RenderThread/R8 Process.setThreadPriority / SchedHints / RenderScript
iOS QoS / GCD / Metal Compute qos_class_t / dispatch_async / MTLComputeCommandEncoder
Web idle 调度 / Worker / OffscreenCanvas / WASM requestIdleCallback / new Worker() / WebAssembly.instantiate
嵌入式 RT 调度 / 中断分级 / NEON sched_setscheduler(SCHED_FIFO) / arm_neon.h

# 6.5 优先级判定(ROI 公式)

参考 卷零·01 §4.3:

ROI = (P95 改善 × 影响用户比例) / (开发成本 × 风险系数 × 可读性损失)
1

推荐执行顺序(先易后难):

优先级 类别 典型操作 收益区间
P0(必做) 主线程 IO 异步化、StrictMode 治理 50-200%
P0(必做) 反射/正则缓存 一行代码改动 50-100×
P1 算法降阶 哈希索引、批量化 10-100×
P1 数据布局 AoS→SoA、cache 对齐 2-15×
P2 协议升级 JSON→Protobuf 3-10×
P2 线程池 大小科学化、协程化 1.5-3×
P3 分支/SIMD 仅热点 2-5×
P3 绑核/QoS 关键路径专用 1.5-4×

铁律:不要从 P3 开始;P0/P1 没做完之前,P2/P3 大概率是过度优化。

# ▶▶ 案例回扣 5(礼物墙的优化执行栈)

按 ROI 顺序在礼物墙落地:

阶段 操作 单步收益 累计 FPS
起点 — — 12
Day 1 主线程图片解码改异步 -22% 主线程 22
Day 1 LongAdder 替 AtomicLong(伪共享) -8% CPU 28
Day 2 GiftItem AoS → SoA IPC 0.7→1.4 38
Day 2 JSON → Protobuf -15% CPU 44
Day 3 关键路径绑大核 -28% 单帧 51
Day 3 飘字 NEON 化 -65% 飘字耗时 56
终点 — — 56

对比 3 周经验派:经验派加了缓存、池化、网络协议——这些都是"看起来正确但与根因无关"的动作。方法派直接命中"IPC 低 + 主线程满"两个真因,3 天搞定。

# 07.实战案例

本章是贯穿案例(§00.5)的最终收口。前面五章用方法论拆解了根因和策略,这里展示完整的优化执行 + 数据验证。

# 7.1 礼物墙优化最终结果

# 7.1.1 优化前后核心指标

实验环境:千元机(骁龙 695,Android 12),3000 用户压测:

指标 优化前 优化后 改善
FPS 平均 12.4 56.8 +358%
FPS P5(最差) 4.2 51.3 +1121%
主线程 P95 帧时长 80 ms 18 ms -78%
主线程 P99 帧时长 130 ms 25 ms -81%
CPU% 均值 95.4% 62.1% -35%
IPC 0.7 1.6 +129%
L1 cache miss rate 27% 4.1% -85%
上下文切换/秒 28000 9500 -66%
大核驻留率 41% 88% +115%

# 7.1.2 五项优化各自贡献

用 A/B 实验(每项单独开启)量化每个策略的实际收益:

优化项 单独 FPS 提升 主要影响维度
主线程图片解码异步化 +10 (12→22) 指令数(错峰)
LongAdder 替 AtomicLong +6 (22→28) IPC(伪共享)
AoS → SoA 数据布局 +10 (28→38) IPC(局部性)
JSON → Protobuf +6 (38→44) 指令数
关键路径绑大核 +7 (44→51) 频率
飘字 NEON 化 +5 (51→56) IPC(SIMD)

重要发现:前 3 项(影响 IPC)合计贡献 +26 FPS(占总收益 60%)——这恰好是经验派完全错过的维度。

# 7.1.3 业务回归

  • 送礼成功率:从 71.2% 恢复至 99.4%
  • 礼物墙投诉率:从 1.83% 降至 0.07%(与基线持平)
  • 送礼 GMV:高峰期同比转正 +14%
  • 副作用:包体 +180KB(Protobuf 库 + NEON 实现),耗电 +1.2%(绑大核),均在预算内

# 7.1.4 灰度与防劣化

按 卷零·06 防劣化 设计三道防线:

开发期:StrictMode 主线程 IO 拦截 + Lint 规则
   ↓
CI:每次 PR 跑礼物墙基准(IPC ≥ 1.4 / FPS ≥ 50 阻断)
   ↓
线上:直播间 FPS / IPC SLO 监控,异常 5 分钟告警
1
2
3
4
5

灰度 14 天,无新增异常,全量发布。

# 7.2 跨端同构案例:JSON → Protobuf

现象

  • 启动期 JSON 解析占 CPU 35%,三端皆有
  • Android:450ms / iOS:380ms / Web:520ms

度量与归因

按 §4.1 决策树走:

  1. RED-D:启动单步耗时高
  2. USE-U:CPU 100%(user 时间占主导)
  3. on-CPU 火焰图:三端共同热点 —— 字段反射 + 字符串 → 类型转换

假设与求证

H₁:把 JSON 改为 Protobuf,三端解析耗时降低 ≥ 60%。

平台 JSON Protobuf 改善
Android(Gson) 450ms 95ms -79%
iOS(Codable) 380ms 110ms -71%
Web(JSON.parse) 520ms 150ms -71%

护栏检查:

指标 JSON Protobuf 是否可接受
包体积 +0 +120KB(pb 库) ✅
可读性 高 低(二进制) ⚠️ 需要工具
兼容性 强 中 ⚠️ 需 Schema 管理

结论:成立,建议在性能敏感路径(启动 / 大数据)优先采用 Protobuf。

# 7.3 平台特异案例:Android 启动期小核陷阱

现象(Android 特有)

  • 用户报"应用刚启动头几秒卡顿,之后正常"
  • 监控数据:启动后 5 秒内 CPU 100%,之后回落到 30%

归因

simpleperf 抓启动后 5 秒的 PMU 数据:

时段 频率 IPC 现象
启动后 0–2s 1.9 GHz 0.8 主线程在小核!
启动后 2–5s 3.3 GHz 2.1 已迁到大核

根因:Android 调度器对新进程默认放小核(功耗优先策略),需要观察一段时间后才迁到大核。这段"小核期"导致启动期 CPU 高、感觉慢。

修复

// Application.onCreate 中绑大核(仅启动期)
public class App extends Application {
  @Override
  public void onCreate() {
    super.onCreate();
    Process.setThreadPriority(Process.THREAD_PRIORITY_DISPLAY);
    // 5 秒后恢复默认
    new Handler(Looper.getMainLooper()).postDelayed(() -> {
      Process.setThreadPriority(Process.THREAD_PRIORITY_DEFAULT);
    }, 5000);
  }
}
1
2
3
4
5
6
7
8
9
10
11
12

验证

灰度 7 天后:

指标 修复前 修复后
冷启动 P95 1820ms 1310ms
启动期 IPC 0.8 2.0
启动期大核驻留率 12% 89%
续航影响 — 单次启动 +2mAh,可忽略

这是平台特异问题的典型代表:iOS / Web 不存在此现象,因为它们的调度策略不同。

# 08.防劣化与长效治理

参考 卷零·06 性能预算与防劣化体系。

# 8.1 三道防线总览

开发期 ──► 编译期/CI ──► 上线期/运行期
   │              │              │
   ▼              ▼              ▼
[Lint]      [自动化基准]     [线上 SLO]
1
2
3
4

# 8.2 编码期 Lint

  • 主线程同步 IO 调用 → 警告
  • Executors.newCachedThreadPool → 警告(应改用固定大小)
  • 紧密循环内反射调用 → 警告
  • onDraw / onBindViewHolder 内 new 对象 → 警告

# 8.3 CI 卡口与线上 SLO

CI 卡口:

  • 启动 / 首屏 / 列表滚动 三类基准用例。
  • 每次 PR 跑一次,CPU 利用率均值劣化 > 5% 阻塞合并。

线上 SLO:

  • 主线程 on-CPU% P95 < 60%(中端机切片)。
  • 进程平均 CPU% < 25%(前台)/ < 5%(后台)。
  • 错误预算耗尽 → 冻结新功能。

# 09.跨平台对照速查

# 9.1 工具速查

用途 Android iOS Web 嵌入式
实时 CPU% top / dumpsys cpuinfo Activity Monitor / top DevTools Performance top / htop
函数级 Profile simpleperf Instruments Time Profiler DevTools Performance perf
系统级 trace Perfetto / Systrace Instruments DevTools ftrace / lttng
PMU 计数器 simpleperf Instruments CPU Counters 不可访问 perf stat
火焰图生成 simpleperf + FlameGraph Time Profiler 直出 DevTools Bottom-Up perf + FlameGraph

# 9.2 关键 API 速查

平台 关键 API
Android Process.getElapsedCpuTime() / Debug.threadCpuTimeNanos() / /proc/[pid]/stat / Process.setThreadPriority
iOS task_info / thread_info / pthread_set_qos_class_self_np / mach_absolute_time
Web performance.now() / performance.mark / performance.measure / Performance Observer
嵌入式 clock_gettime(CLOCK_MONOTONIC) / sched_setaffinity / getrusage

# 10.方法论沉淀

# 10.1 五条核心原则

  1. CPU 优化的本质不是降利用率,是提高单位 CPU 的产出。利用率 80% 跑有用计算 ≫ 利用率 30% 跑无用功。
  2. 性能黄金公式三个变量都要看:指令数(多)/ IPC(缓存)/ 频率(核 + 热)。业界 90% 教程只讲第一个;本文礼物墙案例证明,IPC 维度往往是最大矿藏。
  3. 利用率 vs 负载必须分清:利用率看忙不忙、负载看队伍长。低利用率 + 高负载 = 不是 CPU 问题。
  4. on-CPU 与 off-CPU 必须分清:火焰图找 on-CPU 热点,sched_switch 找 off-CPU 等待。
  5. 求证 > 经验:性能优化结论必须带"前后数据 + 置信区间 + 适用边界"。礼物墙案例是最好的证据:经验派 3 周失败、方法派 3 天解决。

# 10.2 五个常见误区

  • ❌ 用旗舰机基准定 SLO(小核可能慢 4 倍)。
  • ❌ 把所有任务都开多线程(多线程不是免费午餐,实验二)。
  • ❌ 只看指令数,不关心缓存(数据布局影响 5-15×,实验三)。
  • ❌ CPU 高就是有问题(可能是健康的高利用率)。
  • ❌ 一次优化后就不管(业务迭代会让 CPU 重新劣化)。

# 10.3 贯穿案例的方法论提炼

礼物墙案例完整演示了"分析 → 探索 → 优化 → 结果"的科学流程:

阶段 方法 关键产出
分析 重定义问题(§01)+ 黄金公式拆 CPU 预算(§02) "FPS 跌"重定义为"P95 帧时长 80ms + IPC 0.7"
探索 决策树归因(§04)+ PMU 双重验证 锁定根因为 IPC 低(cache miss + 主线程满)
优化 按 ROI 顺序执行 6 项策略(§06) 3 天内每天交付一批,每批可量化
结果 A/B 实验量化每项贡献(§07) FPS 12→56;IPC 0.7→1.6;CPU% 95→62

最重要的方法论财富:永远不要凭直觉改代码——先用方法论建立"问题 → 度量 → 假设 → 实验 → 结论"的闭环。

# 10.4 延伸阅读

  • 《Computer Architecture: A Quantitative Approach》Hennessy & Patterson —— CPU 性能的圣经
  • 《Systems Performance》Brendan Gregg —— off-CPU 分析章节
  • 《Performance Engineering of Software Systems》MIT 6.172 公开课 —— 缓存与并发优化经典
  • Brendan Gregg "USE Method" 博文:https://www.brendangregg.com/usemethod.html
  • ARM big.LITTLE 调度机制白皮书
  • Android simpleperf 文档:https://developer.android.com/ndk/guides/simpleperf
  • WWDC 'Optimize Performance with Time Profiler'
  • Intel "Avoiding and Identifying False Sharing Among Threads" 白皮书

# 11.探索性思考:CPU 性能的"反直觉"再追问

# 11.1 为什么"CPU 利用率 100%"有时是好事

直觉:100% = 危险。但 100% + 高 IPC(>1.5)+ 短任务队列 = CPU 在做有效工作,是健康状态。真正危险是 100% + 低 IPC(<0.8)—— 表示 CPU 都在等内存、等锁、等 IO。

追问:如何区分?看三件套:utilization + IPC + load average。

# 11.2 为什么"多核"不等于"快 N 倍"

阿姆达尔定律:可并行比例 P 时,N 核加速比 = 1 / ((1-P) + P/N)。即使 P=90%、N=8,加速比也只 4.7×,不是 8×。

追问:为什么 P 难以达到 100%?答:① 共享资源(内存、缓存);② 同步开销(锁、屏障);③ 串行启动开销(任务分发);④ 数据依赖。最优做法:识别可并行段,针对性提升 P。

# 11.3 为什么"big.LITTLE 大核"未必更快

直觉:大核 > 小核。但 ARM big.LITTLE 调度时:

  • 后台任务被钉在小核(省电)
  • 用户感知任务才上大核(性能)
  • 错误绑核(如把游戏循环钉到小核)会导致性能腰斩

追问:什么时候应主动控制 affinity?答案:游戏主循环、解码线程、动画 driver——这些都需要稳定大核。

# 11.4 为什么"缓存优化"是 IPC 提升的最大杠杆

实验三证明:同样指令数、不同数据布局,IPC 差 5-15×。原因:DRAM 访问 ~100ns(约 300 个 CPU 周期),L1 cache 仅 1ns。一次 cache miss = 损失 300 条指令的执行机会。

追问:为什么大多数程序员没意识到?因为 CPU 缓存对应用层透明——直到性能瓶颈出现。这是"现代 CPU 性能模型与朴素心智模型的鸿沟"。

# 11.5 为什么"温度"是性能的隐形杀手

CPU 持续高负载 → 温度 ≥ 85°C → 系统降频(thermal throttle)→ 频率降 30-50% → 性能崩。但温度数据通常不在监控里。

追问:如何防御?

  • 监控 /sys/class/thermal/thermal_zone*/temp
  • 高温降级(关动画、降分辨率)
  • 任务时间盒化(一段计算后让 CPU 休息)

# 11.6 反直觉问题清单的最终回应

# 问题 答案 章节
① CPU 100% 一定不好? 不一定,看 IPC §11.1
② 多核就能快 N 倍? 阿姆达尔限制 §11.2
③ 大核一定快? 看 affinity §11.3
④ 缓存优化重要吗? 影响 5-15× §11.4
⑤ 温度会影响 CPU? 降频 30-50% §11.5
⑥ 减少代码就能省 CPU? 只在指令数维度,IPC 不一定 §02
⑦ 火焰图能看完所有问题? 看不到 off-CPU §04
⑧ CPU 优化能"一劳永逸"? 业务迭代会重新劣化 §08

# 12.演进展望:CPU 性能监控的下一个五年

# 12.1 PMU 数据普及到客户端

  • Android 14+ 已开放部分 PMU 计数器给应用。
  • 未来:IPC、cache miss、branch miss 等硬件指标会直接进入线上监控。
  • 意义:从"利用率监控"升级为"效率监控"。

# 12.2 异构 CPU 架构(ARMv9 / AppleSilicon)

  • ARMv9:SVE2 矢量指令、Confidential Computing
  • Apple M 系列:自研 P/E 核 + 极强 IPC(5+)
  • 趋势:算法能否利用 SIMD 决定 2-4× 性能差距。

# 12.3 eBPF 从内核到用户态

  • eBPF 可在内核精确追踪 sched_switch / page fault / syscall。
  • Android 14+ 开放部分 eBPF 给应用。
  • 未来:off-CPU 分析从"采样推测"变成"事件精确"。

# 12.4 LLM 辅助归因

  • 火焰图 + 指标 → LLM → 自动给出"可能根因 + 修复 SQL/代码"。
  • 已有原型:Sentry / Datadog 在试。

# 12.5 协程 / 异步运行时的 CPU 模型

  • Kotlin Coroutines / Swift async 普及后,"线程"概念被抽象。
  • 但 CPU 仍是物理单位——监控工具需要在协程层做聚合。

# 13.跨段权衡哲学:CPU 优化的"零和博弈"地图

# 13.1 七大经典权衡

权衡 A 端 B 端 决策依据
吞吐 vs 延迟 批量处理 立刻响应 后台 → A,前台 → B
算力 vs 内存 预计算缓存 按需算 内存富 → A
CPU vs GPU CPU 计算 GPU 加速 数据并行 → B
大核 vs 小核 大核高频 小核省电 用户感知 → A
多线程 vs 单线程 并行 串行 可并行段 P > 50% → A
指令多 vs IPC 高 减指令 提缓存 计算密集 → A,访存密集 → B
预编译 vs 解释 AOT JIT 启动慢但运行快 → A

# 13.2 决策的"三问法"

  1. 你优化的是吞吐还是延迟?
  2. 你的瓶颈在指令数、IPC 还是频率?
  3. 能用一次实验在 30 分钟内验证吗?

# 14.错误模式库:30 个 CPU 反模式速查

# 14.1 算法 / 数据结构反模式(10 项)

  1. 嵌套循环 O(N²) 处理千级数据 → 排序后 O(N log N)。
  2. String 拼接用 + → StringBuilder。
  3. List 装箱 → IntArray / SparseIntArray。
  4. HashMap 频繁扩容 → 预设 initialCapacity。
  5. 每次 new ArrayList<>(0) → reuse 或 EMPTY_LIST。
  6. 链表代替数组(无频繁中间插入时)→ 数组缓存友好。
  7. 递归代替迭代 → 栈溢出 + 函数调用开销。
  8. 正则全局编译 → 预编译 Pattern。
  9. JSON.parse 大对象 → 流式或字段抽取。
  10. for-in 迭代数组 → 索引迭代缓存友好。

# 14.2 并发 / 锁反模式(5 项)

  1. synchronized 整个方法 → 缩小锁范围。
  2. 共享原子变量频繁更新 → 分段聚合(LongAdder)。
  3. CountDownLatch 等待主线程 → 倒置依赖。
  4. 手写双检锁单例 → 用 lazy / static initializer。
  5. 多线程写同一缓存行 → false sharing → padding。

# 14.3 缓存 / 数据布局反模式(5 项)

  1. AOS(Array of Struct)跨字段访问 → SOA(Struct of Array)。
  2. 链表节点零散分配 → object pool 连续分配。
  3. 稀疏 HashMap → 数组 + 索引。
  4. 大对象按字段对齐失败 → @Aligned 注解。
  5. 频繁创建短生命对象 → 复用池。

# 14.4 调度 / Affinity 反模式(5 项)

  1. 关键线程跑小核 → 设置 affinity 大核。
  2. 后台任务跑大核 → 设置低优先级 + 小核。
  3. GC 阻塞主线程 → 减少分配 / 用值类型。
  4. 动画驱动线程被抢占 → 提升优先级。
  5. 过多线程争抢 CPU → 限制 worker 数 = 核数。

# 14.5 监控 / 度量反模式(5 项)

  1. 采样间隔 1 秒 → 错过短峰值。
  2. 只看 user time 不看 sys time → 漏 syscall 开销。
  3. 只看主进程不看子进程 → 漏渲染进程 / Service。
  4. 不分前后台 → 后台高 CPU 是耗电问题。
  5. 不归因机型 → 大核 / 小核混淆。

# 15.ROI 决策框架:CPU 优化的"先后顺序"

# 15.1 优化项 ROI 排序模板

以礼物墙案例为例:

优化项 CPU 改善 开发成本 风险 ROI
数据结构 SOA 化 -25% 2 人天 低 1
关键线程绑大核 -15% 1 人天 低 2
重复计算缓存 -12% 2 人天 低 3
字符串拼接 → StringBuilder -8% 1 人天 低 4
算法 O(N²) → O(N log N) -5% 3 人天 中 5
GC 减分配 -3% 5 人天 高 6

# 15.2 反向不该做的优化

  • 全部代码改 SIMD(学习成本爆炸)
  • 重写为 Rust(迁移风险大)
  • 全部数据结构 SOA(业务代码可读性下降)

# 16.组织协同模式:CPU 优化是团队工程

# 16.1 四方角色

   产品 ─── 制定性能 SLO(CPU 预算 / 帧时长)
    │
    ▼
   架构 ─── 选型(语言 / 算法 / 框架)
    │
    ▼
   研发 ─── 编码 + Lint + 自测
    │
    ▼
   测试 ─── 多机型 / 多负载验证
1
2
3
4
5
6
7
8
9
10

# 16.2 性能预算机制

每个新功能 PRD 标注:

维度 预算
主线程 CPU% < 30%
子线程 CPU% < 50%
帧时长 P99 < 25ms
IPC(如可测) > 1.0
内存增量 < 30MB

# 16.3 周度雷达

  • TOP 5 高 CPU 页面
  • IPC < 1 的代码段
  • 新增 / 消失的 CPU 热点
  • 各业务线 CPU 占用排名

# 17.可访问性与 CPU:被忽视的维度

# 17.1 无障碍服务的 CPU 开销

  • TalkBack 触发 AccessibilityEvent,每次 CPU +5-10%。
  • 大字体重测量列表项。
  • 高对比度模式触发额外色彩处理。

# 17.2 国际化的 CPU 隐藏成本

  • CJK 字体 fallback 链长,文本测量慢。
  • RTL 切换全量 reflow。
  • 复杂 emoji 分词慢。

# 17.3 老旧机型兼容

  • ARMv7(32 位)单核性能弱 50%。
  • 4 核 vs 8 核 → 关键线程争抢更激烈。
  • 应对:低端机分级降级。

# 18.嵌入式与异构平台特化

# 18.1 车机 / HMI

  • 多核但单核弱(A55 系列)
  • 散热差,温度敏感
  • 多屏共享 CPU

对策:关键路径绑大核 + 散热降级 + 多屏共合成器

# 18.2 IoT / MCU

  • 主频 200MHz,无 OS 调度
  • 无 cache 或极小 cache(4-16KB)
  • 无 SIMD

对策:手写紧凑循环 + 静态分配 + 算法极简

# 18.3 桌面 / Electron

  • 多 Renderer 进程争抢 CPU
  • V8 GC 阻塞
  • IPC 序列化代价高

对策:限制 Renderer 数 + 主动 GC + MessagePort 异步


# 19.自检清单

# 19.1 设计阶段(10 项)

  1. □ 算法复杂度评估完成?
  2. □ 数据结构选型考虑缓存友好?
  3. □ 多线程方案 Amdahl 分析?
  4. □ 关键线程的核绑定策略?
  5. □ 关键路径 CPU 预算?
  6. □ 高负载降级方案?
  7. □ 大数据量下的内存与 CPU 模型?
  8. □ 监控覆盖(CPU% / IPC / load)?
  9. □ 兜底(高温降频)?
  10. □ 多机型分级?

# 19.2 编码阶段(10 项)

  1. □ 避免 String + 拼接?
  2. □ 集合类按场景选?
  3. □ 锁范围最小化?
  4. □ 避免主线程 GC 触发?
  5. □ 数据布局 SOA / AOS 选择?
  6. □ 缓存复用 / 对象池?
  7. □ 监控埋点不影响 CPU?
  8. □ 异步/协程切换明确?
  9. □ Lint 覆盖反模式?
  10. □ 单测覆盖热点函数?

# 19.3 测试阶段(10 项)

  1. □ 低端机跑过基准测试?
  2. □ 多机型 CPU% / FPS 对比?
  3. □ 高温场景验证?
  4. □ 满负载下不卡?
  5. □ 多任务争抢正常?
  6. □ Monkey 测试 CPU 不爆?
  7. □ 弱网弱机组合验证?
  8. □ 大数据量场景过测试?
  9. □ 火焰图无明显平顶?
  10. □ off-CPU 时间合理?

# 19.4 上线阶段(10 项)

  1. □ 灰度 1/5/25/100% 四阶?
  2. □ CPU% / 帧时长 / IPC SLO 告警?
  3. □ 设备维度看板覆盖 95%?
  4. □ 与上版无劣化 > 5%?
  5. □ 灰度期专人值班?
  6. □ 业务指标联动?
  7. □ 客服反馈通道?
  8. □ 回滚预案?
  9. □ A/B 实验样本足?
  10. □ 灰度结论文档归档?

# 20.哲学迁移:CPU 思维的普适性

# 20.1 CPU 三因素 vs 一切性能问题

  • 指令数(做多少事)≈ 网络包数 ≈ 数据库行数 ≈ 渲染层数
  • IPC(每步效率)≈ 网络带宽 ≈ DB QPS ≈ GPU 像素吞吐
  • 频率(速度)≈ 网络带宽 ≈ 磁盘 IOPS ≈ 显示刷新率

CPU 的"三变量法"是普适分析框架——拿到任何性能问题都可以套用。

# 20.2 缓存 vs 内存层级 vs 系统层级

CPU 缓存层级 L1/L2/L3/DRAM,与:

  • 网络层级(CDN / 边缘 / 源站)
  • 数据库层级(内存表 / 磁盘 / 远程)
  • 系统层级(页缓存 / 块缓存 / 网络缓存)

完全同构——距离决定延迟,分级决定吞吐。

# 20.3 调度 vs 资源分配

CPU 调度(CFS / O(1) / EAS)与:

  • 任务队列调度
  • 网络 QoS
  • 数据库连接池
  • 微服务负载均衡

都是"在受限资源下分配工作"问题。学好 CPU 调度,所有调度问题都能借鉴。

# 20.4 元启示

CPU 是计算机的"心脏",所有性能问题终究归结为"CPU 的某种使用方式"。学好 CPU,等于学会了一切。


# 21.一句话哲学

CPU 不是"忙不忙"的问题,而是"忙得有效不有效"的问题。 把"指令数 / IPC / 频率"三个变量同时看清楚,CPU 优化才能真正可量化、可证伪、可持续。 礼物墙案例就是这条原则的最佳证明:经验主义 3 周失败 → 方法论 3 天解决(FPS 12→56、IPC 0.7→1.6、CPU% 95→62)。

CPU 是计算机的心脏,性能是它的脉搏。 学会"指令 + IPC + 频率"的三维诊断,你拿到的不只是 CPU 优化的钥匙,而是一切性能工程的通用心法。

上次更新: 2026/06/07, 10:26:12
性能监控数据治理
内存监控与治理

← 性能监控数据治理 内存监控与治理→

最近更新
01
信号崩溃快速排查
06-15
02
CoreDump破案
06-15
03
perf火焰图实战
06-15
更多文章>
Theme by Vdoing | Copyright © 2019-2026 杨充 | MIT License | 桂ICP备2024034950号 | 桂公网安备45142202000030
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式