编程进阶网 编程进阶网
首页
  • 在线工具
  • JSON工具
  • 文本工具
  • 图片处理
  • 文档转化
  • 代码压缩
  • 加解密
  • 时间日期
  • 网络工具
  • 颜色设计
  • 二维码
  • 开发实用
  • 计算机的原理
  • 操作系统原理
  • 网络协议原理
  • 数据库的原理
  • 序卷导读
  • 数据本质
  • 运行模型
  • 并发设计
  • 内存真相
  • 交互系统
  • 面向对象
  • 设计原则
  • 设计模式
  • 系统架构
  • 技能之旅
  • 体系建设
  • 代码品质
  • 方案设计
  • 稳定可靠
  • 工程运维
  • 性能优化
  • 数据结构导论
  • 线性结构详解
  • 树哈希结构论
  • 容器设计实战
  • 经典算法思想
  • 工程案例剖析
  • 算法题库精练
  • C语言入门
  • C综合案例
  • C专栏博客
  • C标准集库
  • C++入门教程
  • C++综合案例
  • C++专栏博客
  • C++编程技巧
  • Java入门教程
  • Java综合案例
  • Java专栏博客
  • Go入门教程
  • Go综合案例
  • Go专栏博客
  • Go开发技巧
  • JavaScript入门
  • JavaScript案例
  • JavaScript高级
  • Kotlin精通
  • Android库解读
  • Android专栏
  • iOS ObjC入门
  • iOS Swift入门
  • iOS入门精通
  • Web之Html手册
  • Web之TypeScript
  • Web之Vue高级进阶
  • Linux之QML入门
  • Linux之QT核心库
  • Python教程
  • Shell&Bash教程
  • 工具脚本
  • 自动化脚本
  • 质量保障
  • 产品思考
  • 软实力
  • 开发流程
  • Git应用
  • 技术模版
  • 技术规范
  • Markdown
  • Mermaid
  • 开源协议
  • 毛选解读
  • 自我精进
  • 关于我
  • 自我精进
  • 职场管理
  • 职场面试
  • 心情杂货
  • 友情链接

杨充

专注编程 · 终身学习者
首页
  • 在线工具
  • JSON工具
  • 文本工具
  • 图片处理
  • 文档转化
  • 代码压缩
  • 加解密
  • 时间日期
  • 网络工具
  • 颜色设计
  • 二维码
  • 开发实用
  • 计算机的原理
  • 操作系统原理
  • 网络协议原理
  • 数据库的原理
  • 序卷导读
  • 数据本质
  • 运行模型
  • 并发设计
  • 内存真相
  • 交互系统
  • 面向对象
  • 设计原则
  • 设计模式
  • 系统架构
  • 技能之旅
  • 体系建设
  • 代码品质
  • 方案设计
  • 稳定可靠
  • 工程运维
  • 性能优化
  • 数据结构导论
  • 线性结构详解
  • 树哈希结构论
  • 容器设计实战
  • 经典算法思想
  • 工程案例剖析
  • 算法题库精练
  • C语言入门
  • C综合案例
  • C专栏博客
  • C标准集库
  • C++入门教程
  • C++综合案例
  • C++专栏博客
  • C++编程技巧
  • Java入门教程
  • Java综合案例
  • Java专栏博客
  • Go入门教程
  • Go综合案例
  • Go专栏博客
  • Go开发技巧
  • JavaScript入门
  • JavaScript案例
  • JavaScript高级
  • Kotlin精通
  • Android库解读
  • Android专栏
  • iOS ObjC入门
  • iOS Swift入门
  • iOS入门精通
  • Web之Html手册
  • Web之TypeScript
  • Web之Vue高级进阶
  • Linux之QML入门
  • Linux之QT核心库
  • Python教程
  • Shell&Bash教程
  • 工具脚本
  • 自动化脚本
  • 质量保障
  • 产品思考
  • 软实力
  • 开发流程
  • Git应用
  • 技术模版
  • 技术规范
  • Markdown
  • Mermaid
  • 开源协议
  • 毛选解读
  • 自我精进
  • 关于我
  • 自我精进
  • 职场管理
  • 职场面试
  • 心情杂货
  • 友情链接
  • 计算机基础
  • 计算机的原理

    • README
    • 01.计算机组成结构原理
    • 02.计算机存储器的原理
    • 03.计算机基础CPU设计
    • 04.系统CPU缓存的设计
    • 05.计算机输入输出设备
    • 06.计算机总线系统设计
    • 07.计算机指令编程原理
    • 08.计算机程序如何执行
    • 09.计算机内存设计原理
    • 10.计算机二进制和字节
    • 11.计算机异常处理机制
    • 12.计算机IO操作和原理
    • 13.计算机组成原理总结
      • 0.阅读导航
        • 0.1 按场景读
        • 0.2 按层次读
      • 1.第 01 篇·计算机组成结构原理
        • 1.1 速通卡
        • 1.2 展开:冯·诺依曼架构的五根支柱
        • 1.2b 诊断信号灯
        • 1.3 一句话拎走
      • 2.第 02 篇·计算机存储器的原理
        • 2.1 速通卡
        • 2.2 展开:存储层次的设计密码
        • 2.2b 诊断信号灯
        • 2.3 一句话拎走
      • 3.第 03 篇·计算机基础 CPU 设计
        • 3.1 速通卡
        • 3.2 展开:CPU 设计的三重门
        • 3.2b 诊断信号灯
        • 3.3 一句话拎走
      • 4.第 04 篇·系统 CPU 缓存的设计
        • 4.1 速通卡
        • 4.2 展开:缓存设计的四个关键抉择
        • 4.3 一句话拎走
      • 5.第 05 篇·计算机输入输出设备
        • 5.1 速通卡
        • 5.2 展开:I/O 的四个核心机制
        • 5.2b 诊断信号灯
        • 5.3 一句话拎走
      • 6.第 06 篇·计算机总线系统设计
        • 6.1 速通卡
        • 6.2 展开:总线设计的三个时代
        • 6.2b 诊断信号灯
        • 6.3 一句话拎走
      • 7.第 07 篇·计算机指令编程原理
        • 7.1 速通卡
        • 7.2 展开:从 C 代码到机器码的完整旅程
        • 7.2b 诊断信号灯
        • 7.3 一句话拎走
      • 8.第 08 篇·计算机程序如何执行
        • 8.1 速通卡
        • 8.2 展开:CPU 执行流水线的秘密
        • 8.2b 诊断信号灯
        • 8.3 一句话拎走
      • 9.第 09 篇·计算机内存设计原理
        • 9.1 速通卡
        • 9.2 展开:虚拟内存的四重门
        • 9.2b 诊断信号灯
        • 9.3 一句话拎走
      • 10.第 10 篇·计算机二进制和字节
        • 10.1 速通卡
        • 10.2 展开:二进制世界的设计智慧
        • 10.2b 诊断信号灯
        • 10.3 一句话拎走
      • 11.第 11 篇·计算机异常处理机制
        • 11.1 速通卡
        • 11.2 展开:从 CPU 异常到语言异常的完整链条
        • 11.2b 诊断信号灯
        • 11.3 一句话拎走
      • 12.第 12 篇·计算机 I/O 操作和原理
        • 12.1 速通卡
        • 12.2 展开:I/O 模型的四象限
        • 12.2b 诊断信号灯
        • 12.3 一句话拎走
      • 13.全系列·贯通三句话
        • 13.1 速度金字塔——一切性能问题的根源
        • 13.2 局部性原理——所有硬件优化的公理
        • 13.3 分层抽象——计算机科学的终极方法论
      • 14.全系列数据一页纸
      • 15.全系列症状诊断表
      • 16.学习路径建议
    • 14.组成原理思考题答案
  • 网络协议原理

  • 操作系统原理

  • 数据库的原理

  • 计算机
  • 计算机的原理
杨充
2019-07-08
目录

13.计算机组成原理总结

# 计算机组成原理 · 全系列总结

12 篇组成原理文章的核心提炼。每篇一张速通卡、一段展开、一句话带走。

# 0.阅读导航

# 0.1 按场景读

你遇到了什么 先读哪篇
线上接口突然变慢,CPU 很闲但 RT 很高 → §01 组成结构(冯·诺依曼瓶颈)→ §05 I/O 设备
多线程计数器值总是少 → §03 CPU 设计(缓存一致性/MESI)
同样的循环换个嵌套顺序性能差 10 倍 → §04 CPU 缓存(缓存友好编程)
0.1 + 0.2 != 0.3,对账差钱 → §10 二进制和字节(浮点数)
文件下载服务 QPS 上不去 → §12 I/O 操作(零拷贝/epoll)
C 程序偶发 Segfault,查不到原因 → §09 内存设计(虚拟内存/mprotect)
NuMA 机器上进程性能不稳定 → §06 总线系统(numactl)
不知道指令集、流水线、乱序是什么 → §07 指令 → §08 程序执行
想从头建立硬件直觉 → §01 → §02 → 按顺序读完

# 0.2 按层次读

第 0 层·语言基础  →  §10 二进制和字节
第 1 层·全景图    →  §01 组成结构原理
第 2 层·指令      →  §07 指令编程  →  §08 程序执行
第 3 层·处理器    →  §03 CPU 设计  →  §04 CPU 缓存
第 4 层·存储      →  §02 存储器    →  §09 内存设计
第 5 层·互联      →  §06 总线系统
第 6 层·外设      →  §05 I/O 设备  →  §12 I/O 模型
第 7 层·容错      →  §11 异常处理

每层向上依赖:看懂 §08 需要 §07 的指令概念,看懂 §04 需要 §02 的局部性原理。


# 1.第 01 篇·计算机组成结构原理

# 1.1 速通卡

维度 内容
遇到场景 凌晨 3 点线上告警:接口 RT 从 20ms 飙升到 2s,top 显示 CPU 闲(id+wa=91%),但磁盘 wa 高达 71%
暴露问题 小李知道 Java 调优参数,但不知道一次磁盘读要 10ms——对硬件没有感觉,无法建立"代码在硬件上的成本模型"
根因 SSD 随机读延迟从 100μs 涨到 80ms,接口每次请求读几十次小文件——CPU 在等磁盘,不是算得慢
核心知识 冯·诺依曼五大部件(运算器/控制器/存储器/输入/输出)、存储程序概念、哈佛 vs 冯·诺依曼架构、性能公式、功耗墙、阿姆达尔定律
架构主线 不可编程 → 可编程(插线板)→ 存储程序(冯·诺依曼)→ 现代混合架构(CPU 内部哈佛 + 外部冯·诺依曼)
加深理解 如果你写的程序在 htop 上 CPU 100%,你该高兴还是该排查?——CPU 打满说明没在等 I/O,等 I/O 才真正浪费时间

# 1.2 展开:冯·诺依曼架构的五根支柱

五大部件:运算器(ALU)→ 控制器(CU,含 PC/IR)→ 存储器(内存+硬盘)→ 输入设备 → 输出设备。CPU 永远只和内存打交道,外设数据必须先入内存。

存储程序:程序 = 数据,存在内存中,换任务 = 换加载的程序。ENIAC 换任务需数天重新接线,现代计算机只需毫秒级进程切换。

性能公式:CPU 执行时间 = 指令数 × CPI × 时钟周期。三个变量分别由程序员(算法)、CPU 架构师(微架构)、芯片工程师(制程)决定。

瓶颈定律:

  • 冯·诺依曼瓶颈:CPU(~10GHz 级)vs 内存(~50GB/s),CPU 大量时间在等数据
  • 功耗墙:P ∝ V² × f,主频翻倍功耗至少翻倍,行业转向多核/异构
  • 阿姆达尔定律:加速比 = 1/((1-P)+P/N),串行 10% 意味着无限核也只能加速 10 倍

三层直觉:① 速度金字塔(寄存器 0.3ns → 网络 100ms,跨 3 亿倍)② 瓶颈不只在 CPU(95% 的慢是等数据)③ 指令有真实成本(一行 x.y = z 可能触发多次缓存 miss)

# 1.2b 诊断信号灯

信号 你可能该读的章节
接口慢、CPU 闲 §01(瓶颈不在 CPU)→ §05(I/O)
多线程加核不加速 §01(阿姆达尔)→ §03(MESI)
不知道程序在硬件上花了多少钱 §01(全篇就是建立这个直觉的)

# 1.3 一句话拎走

计算机是一台"按地址取指令、顺序执行、数据经内存中转"的机器。记住这三个约束,你就懂了计算机怎么跑。


# 2.第 02 篇·计算机存储器的原理

# 2.1 速通卡

维度 内容
遇到场景 Android 购物车 Bug——用户反馈商品偶尔消失,根因:SharedPreferences.apply() 只到 Page Cache 未落盘
暴露问题 程序员以为"写完了"不等于数据真的在磁盘上——存储层次每两层的"持久化承诺"完全不同
核心矛盾 速度 / 容量 / 成本 不可能三角——快的不够大,大的不够快,又大又快又便宜不存在
核心知识 存储层次金字塔、局部性原理、SRAM vs DRAM、DDR 演进、mmap 零拷贝
加深理解 你程序里 write(fd, buf, 4096) 写完后,数据现在在 CPU 寄存器/内存/Page Cache/磁盘控制器的哪个位置?——答不上来说明你不懂存储层次

# 2.2 展开:存储层次的设计密码

局部性原理是整个缓存体系的基石:

  • 时间局部性:刚访问过的数据很可能再次访问(循环变量 i 每次迭代用到)
  • 空间局部性:访问地址 X 后 X+1 很可能被访问(顺序遍历数组)

SRAM vs DRAM 的电路级差异:SRAM(6 晶体管/bit,~1ns)快但贵;DRAM(1T+1C/bit,~100ns)慢但便宜——Cache 用 SRAM,内存用 DRAM。

mmap 的零拷贝魔法:传统读写 4 次拷贝(磁盘→内核→用户→内核→磁盘),mmap 将文件映射到虚拟地址空间后"写内存 ≈ 写磁盘",省去用户态拷贝。

综合案例日志之旅:四种方案实测对比——方案 D(mmap + 批量写)比方案 C(FileChannel.write)快 160 倍(50ms vs 8000ms)。

# 2.2b 诊断信号灯

信号 可能原因
程序重启后数据丢了 写到了 Page Cache 未 fsync
随机访问大数组比顺序慢几十倍 缓存友好 vs 缓存敌对——§04
top 的 buff/cache 特别大 操作系统利用剩余内存做 Page Cache,正常

# 2.3 一句话拎走

存储层次是计算机最优雅的折中设计。你写的每一行代码,数据都在这个金字塔里上上下下——让热点数据待在塔尖,是性能优化的第一性原理。


# 3.第 03 篇·计算机基础 CPU 设计

# 3.1 速通卡

维度 内容
遇到场景 count++ 多线程值总是少——从 AtomicLong CAS 自旋到 LongAdder 分桶,性能差 10 倍
暴露问题 volatile + AtomicLong 在低竞争下还行,高竞争下 CAS 空转的 CPU 时间比真正的计算还多——多核不是免费午餐
核心矛盾 多核 CPU 共享数据 vs 缓存一致性协议开销——核越多,协同越贵
核心知识 CPU 微架构、CISC/RISC、MESI 协议、伪共享、缓存行填充
加深理解 你写的 synchronized 块里,底层有多少个 CPU 在抢同一个缓存行?——如果你看不到 MESI 的 S 态变 I 态的过程,你就不理解锁的真正开销

# 3.2 展开:CPU 设计的三重门

CISC vs RISC:x86(CISC)指令变长 1-15 字节、支持内存直接操作数;ARM(RISC)定长 32 位、Load/Store 架构。现代 x86 外 CISC 内 RISC——解码时拆成 μops。

MESI 协议:Modified / Exclusive / Shared / Invalid 四种状态,是多核缓存一致性的核心机制。两个核同时写同一缓存行时,协议通过总线嗅探(Snooping)让缓存行在两个核的 L1 之间来回"弹跳"。

伪共享(False Sharing):两个线程各自修改同一缓存行的不同变量,虽然逻辑上不冲突,但 MESI 协议迫使缓存行在 Modified 和 Invalid 之间反复切换——性能可差 10 倍。

LongAdder 解法:@Contended 注解 + 分桶策略,让每个核心往专属的 cell 里累加,把"多核抢一个缓存行"变成"各核写各自的缓存行"。

# 3.2b 诊断信号灯

信号 可能原因
AtomicLong 高并发下吞吐不涨 CAS 空循环,缓存行在多核间弹跳
两个独立 volatile 变量并发修改互相拖慢 伪共享——它们在同一缓存行
加核后吞吐反而下降 MESI 协议开销超过并行收益

# 3.3 一句话拎走

CPU 看着是"一颗",实际是几十亿晶体管组成的复杂系统。多核不是你写多线程就自动加速——缓存一致性、伪共享、CAS 自旋,每一个都可能在偷你的性能。


# 4.第 04 篇·系统 CPU 缓存的设计

# 4.1 速通卡

维度 内容
开篇场景 灰度化 10000×10000 图像——for i / for j(按行)vs for j / for i(按列)性能差 15 倍
根因 按列遍历每次跳一整行,每次访问几乎必然 Cache Miss;按行遍历连续访问,Cache Line 预取命中率接近 100%
核心知识 三级缓存结构、64 字节缓存行、三种地址映射、LRU/伪 LRU 替换策略
终极优化 矩阵乘法:循环交换 + 分块(Tiling)+ SIMD + 预取 = 15 倍加速(12 秒 → 0.8 秒)

# 4.2 展开:缓存设计的四个关键抉择

缓存行为什么是 64 字节? DDR 一次突发传输(Burst Length=8)× 数据总线宽度 8 字节 = 64 字节。多于 64 浪费带宽,少于 64 没充分利用。

三种地址映射:直接映射(冲突多但电路简单)→ 全相联(无冲突但比较器太贵)→ 组相联(8 路是黄金折中)。

替换策略:精确 LRU 需要记录访问顺序(硬件代价大),伪 LRU(二叉树算法)用 N-1 bit 达到 LRU 95% 的效果——实际 CPU 全用伪 LRU。

LRU 的致命缺陷:当工作集刚好大于缓存容量时,LRU 命中率直接跌到 0%(刚淘汰的恰好是下一次要用的)。这就是颠簸(Thrashing)。

# 4.3 一句话拎走

CPU 缓存不是一个"通用的快箱子",而是一个对访问模式极度敏感的系统。连续访问友好、随机访问敌对——你的循环嵌套顺序决定性能。


# 5.第 05 篇·计算机输入输出设备

# 5.1 速通卡

维度 内容
遇到场景 500MB 文件下载服务——read+write 40 秒,换 transferTo(sendfile 零拷贝)后只 5 秒
暴露问题 同样的功能,同样的数据,4 次拷贝 vs 2 次 DMA——数据传输的路径决定了吞吐上限,而不是 CPU 频率
根因 传统读写 4 次数据拷贝 + 4 次上下文切换;sendfile 缩减为 2 次 DMA + 2 次切换
核心知识 四种 I/O 控制方式(查询/中断/DMA/通道)、I/O 接口四大功能、中断全流程
控制演进 程序查询(CPU 100% 空转)→ 中断(按需唤醒)→ DMA(批量搬运绕过 CPU)→ 通道(独立 I/O 处理器)
加深理解 你代码里 Thread.sleep(10) 等 I/O——这 10ms 里 DMA 搬了几百万字节,CPU 可以干几千万次运算。等 I/O 是最高昂的 CPU 闲置

# 5.2 展开:I/O 的四个核心机制

I/O 接口解决四大差异:速度差(CPU GHz vs 键盘 Hz)、格式差(并行 vs 串行)、宽度差(总线 64 位 vs 外设 8 位)、时序差(同步 vs 异步)。

中断处理五步:请求 → 响应(保存 PC/PSW)→ 查中断向量表 → 执行 ISR → 恢复现场返回。

一次磁盘 I/O ≈ 1 亿次 CPU 时钟周期。程序慢几乎永远不是因为 CPU 算得慢,而是等 I/O。

零拷贝的三级跳:read+write(4 拷贝)→ mmap(3 拷贝)→ sendfile(2 次 DMA,0 次 CPU 拷贝)。Linux 2.4 后 sendfile 直接从 Page Cache 到网卡,CPU 只做控制不做搬运。

# 5.2b 诊断信号灯

信号 行动
文件传输类服务 CPU 低但慢 检查拷贝次数,上 sendfile
磁盘读写多用户态/内核态切换 iostat 看 %iowait,高了必查 I/O 路径
你写的 I/O 代码在哪个时间烧掉的? 拷贝 > 上下文切换 > 中断 > DMA——先砍大头

# 5.3 一句话拎走

I/O 是计算机五个部件里最慢的——CPU 和磁盘差 10 万倍,和网络差 300 万倍。优化 I/O 比优化计算更值钱。


# 6.第 06 篇·计算机总线系统设计

# 6.1 速通卡

维度 内容
遇到场景 MySQL 双实例同机部署,B 实例 p99 比 A 高 30%——根因:NUMA 跨节点内存访问(本地 80ns vs 远程 150ns)
暴露问题 物理上同机不等于性能上同机——数据放在哪个 CPU 的内存条上,决定了访问延迟差一倍
核心矛盾 速度 × 宽度 × 距离——并行总线受串扰限制频率上限,串行总线用差分信号突破 GHz
核心知识 三组总线信号、串行取代并行的原因、PCIe 分层架构、NUMA、QPI/UPI
演进路径 ISA(8 位)→ PCI(32 位并行)→ PCIe(串行差分)→ QPI/UPI(CPU 直连)
加深理解 numactl --hardware 看一下你有几个 NUMA 节点——然后 numactl --cpunodebind=0 --membind=0 绑一次你的服务,对比 p99

# 6.2 展开:总线设计的三个时代

并行时代的终结:PCI 总线 64 位并行,因线间串扰无法突破 133MHz。PCIe 采用串行差分信号,单 Lane 即达 GHz 级。

PCIe 分层架构:事务层(TLP 包)→ 数据链路层(ACK/重传)→ 物理层(差分信号),完全像网络协议栈。

NUMA 核心概念:每个 CPU 有自己的本地内存,访问远程内存走 QPI/UPI 总线——延迟翻倍。numactl --cpunodebind=0 --membind=0 将进程绑在同一个 NUMA 节点上。

NVMe 读 4KB 穿越 10 段总线路径:从 SSD 的 PCIe Lane → Root Complex → CPU → 内存控制器 → DRAM,每一步都是带宽的串联瓶颈。

# 6.2b 诊断信号灯

信号 行动
同机多进程性能差距大 numactl --show 确认 NUMA 策略
PCIe 设备带宽跑不满 检查 PCIe 链路协商速率(lspci -vv)
跨 Socket 通信频繁 把相关进程绑同一 NUMA 节点

# 6.3 一句话拎走

总线是计算机的"神经系统",决定了数据能在多快的时间内从 A 部件跑到 B 部件。NUMA 架构下,"数据离 CPU 近不近"比"CPU 快不快"更重要。


# 7.第 07 篇·计算机指令编程原理

# 7.1 速通卡

维度 内容
遇到场景 音视频 SDK 在 x86 正常、ARM 上波形"锯齿"——unsigned char 在 ARM AAPCS 下被 promotion 为 int,丢失溢出保护
根因 CISC 和 RISC 平台对整数提升的处理机制不同——同样的 C 代码,不同的指令集,不同的执行结果
核心知识 指令格式(操作码+地址码)、六种寻址方式、编译四阶段、CISC/RISC 设计哲学
六种寻址 立即 → 直接 → 间接 → 寄存器 → 基址 → 变址,从左到右灵活性递增、速度递减
暴露问题 同样 C 代码编译给 x86 和 ARM,指令序列完全不同——你写的"同一行代码"底层是两套指令在跑
加深理解 gcc -S -O2 test.c 看一眼汇编,数一下 printf("hello") 变成了几条指令——一条 C 语句不等于一条机器指令

# 7.2 展开:从 C 代码到机器码的完整旅程

编译四阶段:预处理(#include 展开)→ 编译(C → 汇编)→ 汇编(汇编 → 目标文件 .o)→ 链接(多 .o + 库 → 可执行文件)。

寻址方式的选择逻辑:

  • 常数操作数 → 立即寻址(最快,值在指令里)
  • 全局变量 → 直接寻址(地址在指令里)
  • 数组循环 → 基址 + 变址(灵活但需额外计算)

CISC vs RISC 的胜负手:纯 CISC 和纯 RISC 都没赢。现代 CPU 全走融合路线——x86 外 CISC 内 RISC(解码为 μops),ARM 从 RISC 出发不断加复杂指令(如 NEON SIMD)。

为什么记住 RISC-V? 开源指令集,没有专利负担,模块化设计(基础+扩展),正在吃掉嵌入式 → 加速器 → 轻度服务器市场。

# 7.2b 诊断信号灯

信号 行动
x86 程序跑 ARM 上结果不对 检查整数 promotion 和未定义行为差异
不确定编译器做了什么 objdump -d binary 对比 O0 和 O2
想理解一条语句的真实成本 看它生成的汇编指令数量和类型

# 7.3 一句话拎走

指令集是软硬件的契约。你的 if / for / 函数调用最终都变成一串机器码——编译器负责翻译,但性能上限由指令集和微架构共同决定。


# 8.第 08 篇·计算机程序如何执行

# 8.1 速通卡

维度 内容
遇到场景 StackOverflow 传奇——排序后数组遍历比乱序快 5~6 倍,根因:分支预测准确率差异(排序后 ~99%,乱序 ~50%)
暴露问题 "同样的数据、同样的循环、同样的次数"耗时差 6 倍——CPU 不是公平的:它对你代码的未来走向有"猜测",猜对了快,猜错了慢
核心矛盾 顺序的假象 vs 并行的真相——CPU 在"假装顺序执行"的外表下疯狂并行
核心知识 五级流水线、三种冒险、分支预测(2 位饱和计数器)、乱序执行(ROB)、超标量、SMT
IPC 对照 无流水线 IPC≈0.2 → 五级流水 IPC≈1 → 乱序+超标量 IPC≈4.5,纯架构优化提速 20 倍
加深理解 perf stat -e branch-misses,branch-instructions ./your_program 看分支预测失误率——超过 5% 就该重构分支逻辑

# 8.2 展开:CPU 执行流水线的秘密

五级流水线:IF(取指)→ ID(解码)→ EX(执行)→ MEM(访存)→ WB(写回)。每个时钟周期同时跑五条指令的不同阶段。

三种冒险与解法:

  • 结构冒险(两条指令抢同一硬件)→ 拆分 I-Cache/D-Cache(哈佛架构)
  • 数据冒险(后一条依赖前一条结果)→ Forwarding(旁路),ALU 结果不等写回直接转发
  • 控制冒险(分支指令未决)→ 2 位饱和计数器分支预测

乱序执行的秘密武器:保留站 + ROB(重排序缓冲区)= "前端顺序取指、中间乱序执行、后端顺序提交"。CPU 把串行指令流重新排成最优执行序列。

# 8.2b 诊断信号灯

信号 行动
循环中 if/else 分支规律性差 改成查表、位运算、无分支写法
perf 显示 branch-misses 高 让判断条件有方向性,或去掉不可预测分支
超线程核跑满但真实吞吐不涨 SMT 共享执行单元,两个线程互抢——绑一个

超线程(SMT):单核暴露两套"寄存器状态",同时跑两个线程——操作系统看是两个核,实际共享一套执行单元。性能提升 20-30%。

# 8.3 一句话拎走

你以为的"顺序执行"是 CPU 精心表演的假象。流水线、乱序、超标量、分支预测——这四个词是 CPU 性能的秘密引擎,也是 Meltdown/Spectre 类安全漏洞的温床。


# 9.第 09 篇·计算机内存设计原理

# 9.1 速通卡

维度 内容
遇到场景 C++ 缓存模块偶发 Segfault——mmap 出内存后被 mprotect 改成只执行权限,写入时 MMU 权限校验失败
暴露问题 你以为所有地址都是"可以写的",但 MMU 按页表逐页检查——不是所有的"指针"都指向可写内存
核心矛盾 物理内存有限 vs 多进程同时运行——虚拟内存让每个进程以为自己独占 4GB
核心知识 虚拟内存、分页(4KB/2MB/1GB)、多级页表、TLB、COW、Lazy Allocation
演进路径 裸物理地址 → 分段(外部碎片)→ 分页 → 多级页表(空间换空间)→ TLB 加速
加深理解 malloc(100MB) 后立即 top 看 RES——几乎没涨。再写一遍数组后 top 再看——涨了。这就是 Lazy Allocation

# 9.2 展开:虚拟内存的四重门

虚拟内存解决四大问题:

  1. 地址冲突:两个进程的 0x400000 映射到不同物理页,互不干扰
  2. 内存保护:页表项的 R/W/U/S 标志位让 OS 拒绝非法访问
  3. 内存不够用:不常用页换出到磁盘——这就是 swap
  4. 内存碎片:分页将物理内存切成 4KB 固定块,外部碎片消除

多级页表的智慧:x86-64 四级页表(PGD→PUD→PMD→PTE),只为实际使用的地址空间分配页表——4KB 页的页表开销从 4MB 降到 ~20KB。

TLB 是页表的 Cache:TLB 命中率从 99% 降到 80%,性能下降约 9%——因为每次 TLB Miss 要走完四级页表的内存访问(4×~100ns)。

Lazy Allocation:malloc(1GB) 瞬间返回,并不真正分配物理内存——首次写入才触发缺页异常分配物理页。top 看 VIRT 和 RES 的巨大差距就是这个机制。

# 9.2b 诊断信号灯

信号 行动
Segfault 但 gdb 指向合法地址 检查 mprotect 或 NX bit 的权限设置
VIRT 巨大 RES 很小 Lazy Allocation 正常,也可能是内存泄漏(VIRT 不停涨)
多进程 fork 后性能先好后差 COW 触发后各自复制页面,内存翻倍

# 9.3 一句话拎走

虚拟内存是现代操作系统最伟大的抽象。你的程序以为自己独占 4GB 连续内存,实际上它的数据被切成了成千上万个 4KB 碎片散落在物理内存各处——TLB 就是拼图快照。


# 10.第 10 篇·计算机二进制和字节

# 10.1 速通卡

维度 内容
遇到场景 财务对账差 0.01 元——DOUBLE 累加 12 万次后精度误差被不断放大,应改用 DECIMAL
暴露问题 0.1 在二进制里是无限循环小数——浮点数的"不精确"是数学定理决定的,硬件是清白的
加深理解 htons(1) 在 x86 上返回 0x0100 还是 0x0001?——答不上来说明没理解大小端
根因 0.1 和 0.2 在二进制中是无限循环小数,存储时被截断——浮点数不精确是数学定理,不是 bug
核心知识 二进制物理基础、补码(模运算)、IEEE 754 浮点数、UTF-8 编码、大端/小端
编码演进 原码(0 双重表示)→ 反码 → 补码(让 CPU 只用加法器做减法)

# 10.2 展开:二进制世界的设计智慧

为什么选二进制? 不是因为数学最优——三进制在信息密度上更优(e≈2.718 在理论上最优)。二进制的胜出是物理实现的胜利:只需区分高低电平,容错空间远大于多值逻辑。

补码的数学本质:模 2^n 加法群。A - B = A + (-B 的补码),CPU 的加法器天然支持减法——一套电路,两套语义。

0.1 + 0.2 ≠ 0.3 的底层原因:IEEE 754 单精度 - 1 位符号 + 8 位阶码 + 23 位尾数。0.1 的尾数无限循环,截断后丢失精度。金融计算永远不要用浮点。

UTF-8 的巧妙设计:

  • ASCII(0x00-0x7F):1 字节,完全向后兼容
  • 中文(0x4E00-0x9FFF):3 字节,以 1110xxxx 开头
  • 自同步性:从任意字节开始,最多 3 字节内找到下一个字符边界

大端 vs 小端:大端(网络字节序,高位在低地址)vs 小端(x86,低位在低地址)。两种格式并存是历史遗产——ARPA 选了网络序,Intel 选了主机序。

# 10.2b 诊断信号灯

信号 行动
金额运算结果差 0.01 浮点→DECIMAL/BigDecimal
0.1+0.2 != 0.3 让你惊讶 读 §10 的 IEEE 754 部分
网络通信结构体字节序错乱 发端/收端的大小端不一致——htonl/ntohl

# 10.3 一句话拎走

二进制不是计算机的选择,是三极管的宿命。 开关只有通和断,所以计算机只有 0 和 1。后面的一切——整数、浮点、字符、图片、视频——全是这些 0 和 1 的排列组合游戏。


# 11.第 11 篇·计算机异常处理机制

# 11.1 速通卡

维度 内容
遇到场景 Java 推送服务 QPS 降为 0——JNI 本地库 SIGSEGV 被自定义处理器"吞了",线程池耗尽,进程不死不活
暴露问题 Java 的 NullPointerException 底层是 CPU 的 #PF → OS 的 SIGSEGV → JVM——你的 try-catch 背后是三级接力
根因 JNI 异常处理器忽略了信号,Java 层不知道底层已崩溃,线程卡在 native 调用永不返回
核心知识 四种异常(中断/陷阱/故障/终止)、IDT、信号机制、JVM 异常底层原理
安全基石 Stack Canary / ASLR / NX / CFI 都建立在异常机制和 MMU 之上
加深理解 try { obj.method(); } catch (NullPointerException e) {} 如果 obj==null,这行代码实际触发了 #PF→SIGSEGV→JVM信号处理→创建异常对象→catch捕获——猜一下这中间 CPU 做了多少次上下文切换?

# 11.2 展开:从 CPU 异常到语言异常的完整链条

四种异常的精确区分:

类型 触发 同步/异步 可恢复? 返回位置 典型例子
中断 外部设备 异步 是 下一条 键盘按键、网卡收包
陷阱 int 0x80 同步 是 下一条 系统调用
故障 指令执行出错 同步 可能 当前指令 缺页异常、除零
终止 硬件严重错误 同步 否 不返回 硬件故障、双重故障

缺页异常是"可恢复故障"的典范:

  1. CPU 发现页不在内存 → #PF 异常
  2. 异常处理器检查访问合法性 → 分配物理页 → 从磁盘读入 → 更新页表
  3. 返回重新执行触发指令——这次页在内存了,指令成功。整个过程对程序完全透明。

Java NullPointerException 的底层:obj.method() 当 obj==null 时,CPU 尝试访问地址 0x0 → MMU 发现页表无映射 → #PF → OS 发 SIGSEGV → JVM 信号处理器识别为 NPE → 创建 NullPointerException 对象 → 抛出到 Java 栈。从 CPU 异常到 Java 异常的接力,经过了 MMU → OS → JVM 三层。

异常的开销:Java 每秒抛几万次异常会引发 GC 压力(异常对象 + 栈轨迹字符串)。异常应该是"异常",不应该是控制流。

# 11.2b 诊断信号灯

信号 行动
JNI 调用后线程卡死 检查 native 层信号处理器是否吞了致命信号
用异常做控制流(try 代替 if) 每秒 >1万次异常就会引发 GC——改用返回值
Segfault 后 core dump 看不懂 读 dmesg,IDT 向量编号会指向具体异常类型

# 11.3 一句话拎走

异常不是 bug,是计算机的免疫系统。从 CPU 的 #PF 到 Java 的 NullPointerException,中间经过 MMU → 操作系统 → JVM 三层接力——理解这条链,你才能写出"崩溃时能留下线索"的程序。


# 12.第 12 篇·计算机 I/O 操作和原理

# 12.1 速通卡

维度 内容
遇到场景 Netty 静态资源服务卡在 QPS 3000——FileInputStream.read 阻塞 IO + 双拷贝,换 DefaultFileRegion(sendfile)后飙到 18000+
暴露问题 同一个文件、同一个网络——换一条数据搬运路径,QPS 差了 6 倍。IO 模型的选型决定了架构的天花板
核心矛盾 高并发连接 vs 每连接一个线程——C10K 问题催生了 IO 多路复用
核心知识 四种 I/O 模型、epoll(红黑树+就绪链表)、零拷贝、Reactor/Proactor、io_uring
架构演进 Apache 多进程(C10K 上限)→ Nginx epoll + sendfile → io_uring(共享环形缓冲区,终极异步)
加深理解 你的 Web 服务能处理多少并发连接?1K/10K/100K/1M?答不上来说明没理解 IO 模型决定并发上限。C10K 的答案在 epoll 的 O(1) 就绪事件获取里

# 12.2 展开:I/O 模型的四象限

同步/异步 × 阻塞/非阻塞四象限:

              阻塞          非阻塞
同步    BIO(accept 等)   NIO 轮询(浪费 CPU)
        IO 多路复用(select/poll/epoll)——高效等待
异步    AIO(Windows IOCP / Linux io_uring)

epoll 为什么快得不可理喻:

  • select/poll:每次调用传全部 fd 集合到内核遍历 → O(n),最大 1024
  • epoll:红黑树存所有 fd + 就绪链表只存活跃 fd → O(1) 获取就绪事件,无 fd 数限制
  • epoll_create(创建实例)→ epoll_ctl(增删 fd 进红黑树)→ epoll_wait(只从就绪链表取)

Reactor vs Proactor:

  • Reactor(epoll):内核通知"可读了",应用自己调用 read——同步非阻塞
  • Proactor(io_uring):内核读完后通知"数据在缓冲区 X,拿去用"——真异步

io_uring:Linux AIO 的终局。共享环形缓冲区(SQ/CQ),应用往 SQ 丢请求,内核完成后往 CQ 写结果——零系统调用 + 零拷贝 + 批量提交。

# 12.2b 诊断信号灯

信号 行动
文件服务 QPS 瓶颈不在 CPU 检查是否阻塞 IO——切 sendfile
select/poll 到 1024 fd 截断 切 epoll
网络+磁盘双密集但没用到 io_uring 考虑 Proactor,Linux 5.1+ 可用
大量短连接 TIME_WAIT 调整 tcp_tw_reuse+连接池复用

# 12.3 一句话拎走

从 BIO 到 NIO 到 epoll 到 io_uring,I/O 模型的进化史就是"让 CPU 的等待时间趋近于零"的历史。选择一个好的 I/O 模型,比优化算法更能提升系统吞吐。


# 13.全系列·贯通三句话

# 13.1 速度金字塔——一切性能问题的根源

寄存器(0.3ns) → L1(1ns) → L2(5ns) → L3(15ns) → 内存(100ns)
→ SSD(100μs) → HDD(10ms) → 网络(100ms)
差 3 亿倍

程序员的肌肉记忆:遍历连续数组(缓存友好)和随机链表(缓存敌对)性能可差 100 倍;循环里开文件句柄(每次 I/O)和批量读写差 1000 倍。

# 13.2 局部性原理——所有硬件优化的公理

  • 时间局部性 → Cache、TLB、分支预测
  • 空间局部性 → Cache Line 64B 预取、DDR Burst Length=8

你的代码如果同时满足两条局部性,硬件会拼尽全力帮你加速;如果两条都不满足,再好的硬件也救不了。

# 13.3 分层抽象——计算机科学的终极方法论

层 隐藏的复杂性
高级语言 寄存器分配、指令调度
指令集 ISA 流水线、乱序执行、分支预测
虚拟内存 物理页分配、磁盘交换
VFS 具体文件系统(ext4/xfs/nfs)
Socket API TCP 重传、拥塞控制、网卡中断

每一层都向上一层隐藏复杂性,同时定义了性能的上限。写出对硬件有感觉的代码,就是透过抽象看清真实的物理成本。


# 14.全系列数据一页纸

篇 数据 含义
01 5 部件 / 3 瓶颈 冯·诺依曼架构 + 性能公式 + 阿姆达尔
02 7 层金字塔 寄存器 0.3ns ↔ 网络 100ms,差 3 亿倍
03 MESI 4 态 多核缓存一致性:Modified/Exclusive/Shared/Invalid
04 15 倍 矩阵乘法:循环交换 + 分块 + SIMD 的优化幅度
05 1 亿次 1 次磁盘 I/O ≈ 1 亿次 CPU 时钟周期
06 2 倍 NUMA 本地 80ns vs 远程 150ns
07 6 种寻址 立即/直接/间接/寄存器/基址/变址
08 IPC 20 倍 无流水线 0.2 → 全优化 4.5
09 4 级页表 x86-64:PGD→PUD→PMD→PTE,每级 9 bit
10 0.1+0.2≠0.3 IEEE 754 浮点精度限制,非 bug 是定理
11 4 类异常 中断/陷阱/故障/终止,从 CPU 到语言异常 3 层接力
12 18000 QPS sendfile 零拷贝 vs 传统 IO 的 6 倍吞吐差距

# 15.全系列症状诊断表

对照你的代码症状,直接跳到对应篇章。

你看到的症状 第一反应读这篇 第二篇
接口慢、CPU 闲、%iowait 高 §01 瓶颈不在 CPU → §05 I/O 设备 §12 sendfile/epoll
volatile 变量间互相拖慢 §03 伪共享 → §04 缓存行
遍历二维数组 i/j vs j/i 差 10 倍 §04 空间局部性 → §02 缓存行
多线程计数器不准、AtomicLong CAS 空转 §03 MESI → §03 LongAdder
malloc(1GB) 瞬间返回但 top RES 很小 §09 Lazy Allocation
C++ 程序偶发 Segfault,dmesg 看得到 §11 异常分类 → §09 虚拟内存保护
财务对账差 0.01 §10 IEEE 754 → DECIMAL
NUMA 机器上进程性能不稳定 §06 numactl → §02 存储局部性
0.1 + 0.2 != 0.3 §10 浮点原理
循环里开 new FileInputStream §12 BIO vs NIO → §05 DMA
服务 QPS 卡在 3000 不上不下 §12 epoll + sendfile
分支多的循环特别慢 §08 分支预测 → 改无分支写法
gcc -O0 和 -O2 生成代码完全不一样 §07 编译四阶段 → §08 乱序执行
小程序 RES 几个 G §09 虚拟内存 → 可能是泄漏

# 16.学习路径建议

如果你想两天速通:读 §0 导航 → 每篇只看 §N.1 速通卡 + §N.3 一句话拎走 → 遇到具体问题再展开。

如果你想系统建立硬件直觉:按层次顺序读(§10→§01→§07→§08→§03→§04→§02→§09→§06→§05→§12→§11),每读完一篇在脑海里跑一遍"我写的 a = b + c 到屏幕上看到结果之间经过了哪些部件"。

如果你正在排查性能问题:先看 §15 症状诊断表找到对应篇章,看速通卡确认方向,看信号灯验证假设,再看展开加深理解。

自测——每篇读完的标准:合上文档后能对着空气讲一遍"这篇的核心矛盾是什么、最重要的设计取舍是什么、给我一句能带到下周工作中的话"。

上次更新: 2026/07/10, 14:57:20
12.计算机IO操作和原理
14.组成原理思考题答案

← 12.计算机IO操作和原理 14.组成原理思考题答案→

最近更新
01
11.数据库的原理总结
07-12
02
11.操作系统原理总结
07-10
03
资深程序员软能力
07-10
更多文章>
Theme by Vdoing | Copyright © 2019-2026 杨充 | MIT License | 鄂ICP备2024073355号-1 | 鄂ICP备2024073355号
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式