Chapter 1 微处理器与数据表示
1.1 从存储程序到微处理器
1.1.1 程序也可以作为数据存储
早期电子计算机通过接线改变计算过程。存储程序(Stored Program)思想把指令编码后放入存储器,机器可以按顺序取指、译码和执行,改变程序不再需要重新接线。(第 3–7 页)
机器语言(Machine Language)直接使用指令的二进制编码;汇编语言(Assembly Language)使用 MOV、ADD 等助记符,由汇编器(Assembler)翻译成机器码。高级语言进一步使用表达式、控制结构和数据抽象,由编译器完成更复杂的翻译。
flowchart LR
A[高级语言] -->|编译| B[汇编表示]
B -->|汇编| C[机器指令]
C --> D[取指、译码、执行]
D --> E[寄存器与内存状态变化]
这是概念上的翻译链。实际工具链不一定把每一步都保存为独立文件。
1.1.2 x86 演进中的关键变化
课件第 8–30 页按历史介绍了以下处理器。复习时应把型号与引入的机制对应起来,而不只记年份。
| 处理器或系列 | 课件所强调的变化 | 理解重点 |
|---|---|---|
| Intel 4004(1971) | 4 位微处理器 | CPU 功能集成到芯片;4 位为半字节 |
| 8086/8088(1978/1979) | 16 位寄存器、20 位地址、分段 | 地址空间为 \(2^{20}\) 字节,即 1 MiB |
| 80286(1982) | 保护模式、24 位物理地址 | 描述符、权限检查,最大 16 MiB 物理地址空间 |
| 80386(1985) | 32 位寄存器、分页、虚拟 8086 模式 | IA-32 编程模型与 4 KiB 页 |
| 80486(1989) | 更完善的流水线、片上 L1 缓存 | 集成程度提高;课件的 FPU 描述对应带 FPU 的型号 |
| Pentium(1993) | 双流水线、分支预测、分离的指令/数据缓存 | 超标量执行与缓存一致性 |
| P6 系列 | 乱序执行,后续型号加入 MMX、SSE | 指令级并行与向量处理 |
| Pentium 4 及其后续型号 | SSE2、超线程、Intel 64 等逐步加入 | 同一系列不同型号的功能不完全相同 |
| 多核处理器 | 单个封装中包含多个执行核心 | 核心并行与线程并行 |
8086 的外部数据总线是 16 位,8088 为 8 位,但两者都具有 16 位寄存器与 20 位地址。寄存器位宽、数据总线位宽和地址位宽是不同概念。
8086 的四个段寄存器同时提供四个段窗口,每段最多 64 KiB。四个窗口可以重叠,因此“最多覆盖 256 KiB”不是四块独立内存的保证,更不是处理器总地址空间只有 256 KiB。
1.2 提高处理器性能的几种机制
1.2.1 流水线、超标量与乱序执行
流水线(Pipeline)把指令处理划分成多个阶段,让不同指令同时处在不同阶段。它主要提高连续指令的吞吐率,不等价于缩短单条指令的所有阶段总延迟。
超标量(Superscalar)处理器可以在一个周期内向多个执行通路分派指令。但若后续指令都依赖前一条结果,增加通路也未必能提高利用率。(第 16–22 页)
; 用于说明数据依赖的示意代码
ADD EAX, EBX ; 更新 EAX
IMUL EAX, ECX ; 依赖上一条的结果
ADD EDX, ESI ; 与前两条没有寄存器数据依赖
乱序执行(Out-of-Order Execution, OoOE)会在一定指令窗口中寻找操作数已准备好的指令。示例中,最后一条可能先于尚在等待的乘法执行。硬件仍须维护程序可见的正确结果;通常通过按程序顺序提交结果实现精确状态。
课件把复杂 x86 指令的内部处理描述为“CISC 转换到 RISC 核”。更准确的理解是:许多实现把指令译成较简单的微操作(Micro-operation, μop);微操作与微码(Microcode)不是同义词,不能据此推断每条指令都经过微码程序。
1.2.2 SIMD:一条指令操作多个数据元素
单指令多数据(Single Instruction, Multiple Data, SIMD)把一个宽寄存器视为多个数据通道,对各通道执行相同操作。(第 23–25、82 页)
例如,一个 128 位寄存器可存放 4 个 32 位单精度浮点数:
| 寄存器类别 | 宽度 | 可容纳的 32 位元素数 |
|---|---|---|
| MMX | 64 位 | 2(这里指整数打包,非 MMX 浮点运算) |
| XMM | 128 位 | 4 |
| YMM | 256 位 | 8 |
| ZMM | 512 位 | 16 |
MMX、SSE、AVX 等扩展支持的元素类型和具体运算不同,不能仅凭寄存器宽度推断某条指令是否存在。字符串、位串和位域也是 x86 处理的数据形式。
1.2.3 超线程与多核
超线程(Hyper-Threading, HT)在一个物理核心上提供多个逻辑处理器。每个逻辑处理器保留自己的架构状态,如通用寄存器和指令指针,同时共享核心中的许多执行资源。(第 26–29 页)
多核(Multi-core)则提供多个物理执行核心。二者可以结合:4 核、每核 2 个硬件线程通常表现为 8 个逻辑处理器,但不意味着任意任务都能获得 8 倍性能。
| 机制 | 并行对象 | 主要限制 |
|---|---|---|
| 流水线 | 不同指令的不同阶段 | 阶段不平衡、停顿 |
| 超标量/乱序执行 | 同一线程中可独立执行的指令 | 数据依赖、执行资源 |
| SIMD | 多个数据元素 | 数据布局、分支与向量宽度 |
| 超线程 | 同一核心上的多个线程 | 共享资源竞争 |
| 多核 | 不同核心上的线程 | 串行部分、同步和内存带宽 |
课件中的超线程面积与性能百分比来自特定历史测试,不应当作所有处理器和负载的固定收益。
1.2.4 Intel 64 的含义
Intel 64 引入 IA-32e 模式,其中包括运行旧程序的兼容模式和使用扩展寄存器的 64 位模式。64 位寄存器不意味着处理器已经实现全部 \(2^{64}\) 字节虚拟地址或物理地址;实际位数、规范地址形式和分页层级见 第 2 章。(第 30 页)
1.3 进制与数值转换
1.3.1 位权表示
基数(Radix)为 \(r\) 的数,用各位数字与位权相乘后求和:
例如:
二进制每位取 0 或 1;十六进制用 0–9、A–F,每位恰好对应 4 个二进制位。MASM 常用后缀 H、B 表示十六进制与二进制,例如 0FFH、1010B。(第 31、64–72 页)
1.3.2 十进制转二进制
整数部分采用反复除以 2、余数逆序排列;小数部分采用反复乘以 2、整数部分顺序排列。
课件例题:
对于 \(46.6875\),整数部分 \(46=(101110)_2\),小数部分计算如下:
| 步骤 | 乘以 2 | 取出的二进制位 | 剩余小数 |
|---|---|---|---|
| 1 | \(0.6875\times2=1.375\) | 1 | 0.375 |
| 2 | \(0.375\times2=0.75\) | 0 | 0.75 |
| 3 | \(0.75\times2=1.5\) | 1 | 0.5 |
| 4 | \(0.5\times2=1\) | 1 | 0 |
因此:
并非所有有限十进制小数都有有限二进制展开。例如课件的 \(0.678\) 在截取 12 个小数位时是 \((0.101011011001)_2\),只能写成近似相等。这是截断误差,不能称为数值溢出。
1.3.3 二进制、八进制、十六进制互换
从小数点向两侧分组:八进制每组 3 位,十六进制每组 4 位。整数部分在最左侧补零,小数部分在最右侧补零。
课件的跨进制例题:
分组能够直接转换,是因为 \(8=2^3\)、\(16=2^4\)。
1.4 字符编码与 BCD
1.4.1 字符不等于字符编码值
ASCII(American Standard Code for Information Interchange)是 7 位字符编码,包含 128 个码值。'A' 的 ASCII 值为 41H,'0' 为 30H;字符 '5' 的编码 35H 与整数 5 不同。(第 32–35 页)
所谓“扩展 ASCII”并不是一套统一标准,不同代码页可能给 80H–FFH 分配不同字符。
Unicode 为字符分配码点(Code Point),UTF-8、UTF-16、UTF-32 则是编码这些码点的方式。课件将 Unicode 简化为“每个字符 16 位”并不普遍成立:UTF-16 可以使用一个或两个 16 位码元;与标准 ASCII 对应的是 U+0000–U+007F,不是整个 00H–FFH 范围。参见 Unicode 编码 FAQ。
1.4.2 BCD 是逐位编码,不是进制转换
二进制编码十进制(Binary-Coded Decimal, BCD)用 4 位编码一个十进制数字,所以合法数字是 0000–1001。1010–1111 不能直接作为一个十进制数位。(第 36–37、80 页)
| 表示对象 | 十进制 13 的表示 |
|---|---|
| 普通二进制整数 | 00001101B,即 0DH |
| 压缩 BCD(Packed BCD) | 0001 0011B,即 13H |
| 非压缩 BCD(Unpacked BCD) | 两个字节,各自低 4 位分别放 1 和 3 |
ASCII 字符串 "13" |
31H 33H |
压缩 BCD 每字节放两个十进制数字,非压缩 BCD 每字节放一个。BCD 便于逐位十进制处理,但一般算术需要进行十进制调整,不能把 BCD 编码当作普通二进制整数直接解释。
1.5 无符号数、补码与溢出
1.5.1 同一位串的两种解释
对于 \(n\) 位二进制位串 \(b_{n-1}\cdots b_0\),无符号值为:
若按二进制补码(Two's Complement)解释,最高位权重变为负数:
例如 8 位 80H 作为无符号数是 128,作为补码是 \(-128\);FFH 分别表示 255 与 \(-1\)。内存中没有自动附带的“有符号”标签,含义取决于使用这些位的指令与程序约定。(第 38–40 页)
1.5.2 原码、反码与补码
| 表示方法 | 负数编码规则 | 8 位范围 | 零 |
|---|---|---|---|
| 原码(Sign-Magnitude) | 符号位加绝对值 | \(-127\) 到 \(127\) | 正零、负零 |
| 反码(One's Complement) | 正数各位取反 | \(-127\) 到 \(127\) | 正零、负零 |
| 补码(Two's Complement) | 各位取反再加 1 | \(-128\) 到 \(127\) | 只有一个零 |
一般基数 \(r\)、位数 \(n\) 下,减基数补码为 \((r^n-1)-N\),基数补码为 \((r^n-N)\bmod r^n\)。在二进制中分别对应反码和补码。(第 73–79 页)
以 8 位的 115 为例:
正数 115:01110011
逐位取反:10001100
再加上 1:10001101 → 表示 -115
补码让减法可以通过加法实现:
例如 \(5-7\) 的 8 位运算为 05H + F9H = FEH,按补码解释得到 \(-2\)。
1.5.3 进位和有符号溢出不同
FFH + 01H 的 8 位结果是 00H,有无符号进位;若按补码解释,则是 \(-1+1=0\),没有有符号溢出。
7FH + 01H 的结果是 80H,没有最高位进位;若按补码解释,\(127+1\) 超出上界,发生有符号溢出。两类情况分别关联 CF 和 OF,见 第 2 章的标志位。
硬件补码加法的回绕语义不自动成为 C/C++ 有符号整数的语言语义,二者的区别见 课程导论。
1.6 数据大小、小端序与 MASM 声明
1.6.1 x86 数据大小
| 名称 | 位数 | 字节数 | 常见 MASM 声明 |
|---|---|---|---|
| 字节(Byte) | 8 | 1 | DB / BYTE |
| 字(Word) | 16 | 2 | DW / WORD |
| 双字(Doubleword) | 32 | 4 | DD / DWORD |
| 四字(Quadword) | 64 | 8 | DQ / QWORD |
| 十字节数据 | 80 | 10 | DT / TBYTE |
x86 的 WORD 始终指 16 位,不随处理器进入 64 位模式而改为 64 位。MASM 还提供 SBYTE、SWORD、SDWORD 等带符号声明,以及 FWORD(48 位)。课件第 47 页的 FDWORD 应为 FWORD,参见 Microsoft 的 FWORD 参考。
1.6.2 小端序
小端序(Little-endian)把多字节数的低有效字节放在低地址。若从 1000H 开始保存双字 12345678H:(第 41–43、81 页)
| 地址 | 保存的字节 |
|---|---|
1000H |
78H |
1001H |
56H |
1002H |
34H |
1003H |
12H |
大端序(Big-endian)则按 12H 34H 56H 78H 存放。字节序描述字节在内存中的先后,不表示每个字节内部的比特需要倒转。
字符数组 DB 'ABCD' 按字符顺序存放 41H 42H 43H 44H。若把这四个字节作为一个小端双字读取,数值是 44434241H;不要把字符序列与整数显示顺序混为一谈。
1.6.3 数据声明与执行指令
count WORD 10
mask DWORD 12345678H
samples WORD 50 DUP(?)
ratio REAL4 0.15625
precise REAL8 0.15625
这些是数据定义伪指令(Directive),指导汇编器分配并初始化空间,不是 CPU 运行时执行的运算。50 DUP(?) 为 50 个字保留空间,即 100 字节;? 表示不在源码中指定初值。
REAL4、REAL8、REAL10 分别对应 4、8、10 字节浮点数据。声明数据宽度与读写指令的操作数宽度必须匹配。
1.7 IEEE 754 浮点数
1.7.1 符号、指数和有效数
浮点表示(Floating-point Representation)借助科学计数法,在固定长度编码中兼顾范围与精度。IEEE 754 的常见二进制格式包含符号位 \(s\)、存储的指数 \(E\) 和小数字段 \(F\)。(第 44–56 页)
| 格式 | 总位数 | 符号 | 指数 | 小数字段 | 规格化有效精度 | 指数偏置 |
|---|---|---|---|---|---|---|
| binary16 | 16 | 1 | 5 | 10 | 11 位 | 15 |
| binary32(单精度) | 32 | 1 | 8 | 23 | 24 位 | 127 |
| binary64(双精度) | 64 | 1 | 11 | 52 | 53 位 | 1023 |
| binary128 | 128 | 1 | 15 | 112 | 113 位 | 16383 |
若小数字段有 \(t\) 位,规格化数(Normal Number)的值为:
最高位的 1 由规格化形式隐含,不保存到小数字段中,因此 binary32 虽然只存 23 位小数,却具有 24 位有效精度。
浮点符号与整数补码的负权最高位不同;指数也按偏置码存储。IEEE 浮点编码是固定宽度,课件第 50 页不能理解为“浮点格式必须是变长结构”。
1.7.2 编码例题:0.15625
按 binary32 编码:
- 数值为正,\(s=0\)。
- 实际指数为 \(-3\),存储指数 \(E=-3+127=124=(01111100)_2\)。
- 去掉隐含的首位
1,小数字段为01000000000000000000000。
0 | 01111100 | 01000000000000000000000
完整位串为 3E200000H,在小端内存中按 00 00 20 3E 存储。反向解码得到 \(1.25\times2^{-3}=0.15625\)。(第 86 页)
1.7.3 零、非规格化数、无穷与 NaN
设指数共 \(e\) 位,最大编码为 \(E_{\max}=2^e-1\)。
| 指数字段 | 小数字段 | 含义 |
|---|---|---|
| \(0<E<E_{\max}\) | 任意 | 规格化数,隐含首位为 1 |
| \(E=0\) | \(F=0\) | 正零或负零,由符号位区分 |
| \(E=0\) | \(F\ne0\) | 非规格化数(Subnormal Number) |
| \(E=E_{\max}\) | \(F=0\) | 正无穷或负无穷 |
| \(E=E_{\max}\) | \(F\ne0\) | 非数(Not a Number, NaN) |
非规格化数不使用隐含的 1,其值为:
binary32 的最小正规格化数是 \(2^{-126}\),最小正非规格化数是 \(2^{-149}\)。后者让接近零的结果逐渐失去精度,而不是直接跳到零,这称为渐进下溢(Gradual Underflow)。(第 51–53 页)
NaN 不是普通实数。典型浮点比较中 NaN == NaN 为假;检测 NaN 应使用语言提供的检测函数。非规格化数的性能影响取决于处理器和浮点控制设置,不能一概认定其运算总是很慢。
1.7.4 舍入与浮点加法
课件列出五种舍入方向:(第 56–57 页)
| 模式 | 含义 |
|---|---|
roundTiesToEven |
舍入到最近值,恰在中间时选末位为偶数者 |
roundTiesToAway |
舍入到最近值,恰在中间时远离零 |
roundTowardZero |
向零截断 |
roundTowardPositive |
向正无穷方向舍入 |
roundTowardNegative |
向负无穷方向舍入 |
标准中的模式与某条 x86 指令可选择的模式需要区分;不能假设所有硬件操作都提供全部五种。
浮点加法通常经过对阶、有效数相加、规格化和舍入。课件以十进制、7 位有效数字作类比:
最终结果受可存储精度限制,和数学上的实数加法不同。
1.7.5 为什么单精度反复加 1 会停住
binary32 有 24 位有效精度。在 \([2^{24},2^{25})\) 范围,相邻可表示数间隔为 2。于是 \(2^{24}+1\) 恰在 \(2^{24}\) 与 \(2^{24}+2\) 中间,按“最近、偶数优先”舍入会回到 \(2^{24}\)。(第 58–59 页)
float sum = 0.0f;
for (int i = 0; i < 30000000; ++i) {
sum += 1.0f;
}
在每一步都按 binary32、默认舍入模式计算,且没有重排运算时,结果会停在 16777216。这里不是已经达到最大浮点数,而是当前量级下的精度不足以记录加 1 的变化。
1.7.6 数学等价不保证数值效果相同
当 \(x\) 很大时,\(\sqrt{x+1}\) 与 \(\sqrt{x}\) 非常接近,直接相减可能发生消去(Cancellation):
右侧避免了两个近似相等数相减,通常在这个场景下更稳定。课件用 Herbie 的表达式改写作为案例,说明改善精度有时需要改变计算方式,而不只是提高位宽。(第 60 页)
1.8 扩展数据格式与案例
1.8.1 十进制浮点格式
IEEE 754 也定义十进制交换格式。课件第 55 页展示 decimal32、decimal64、decimal128,分别提供 7、16、34 位十进制有效数字。十进制浮点可以精确表示范围和精度允许的十进制小数,但仍然具有有限精度;不要直接套用前面的二进制隐含首位公式。
1.8.2 AI 中的范围与精度取舍
第 61–62、84 页比较了面向训练与推理的格式。
| 格式 | 指数位数 | 小数字段位数 | 主要取舍 |
|---|---|---|---|
| FP32 | 8 | 23 | 较高精度与较宽范围 |
| FP16 | 5 | 10 | 较低存储与运算成本,范围较小 |
| BF16 | 8 | 7 | 保留较宽指数范围,降低有效精度 |
| TF32 | 8 | 10 | 特定张量运算使用的计算精度 |
TF32 的字段对比描述的是计算格式,不能据此把它当作普通的 19 位内存标量类型。课件中的推理速度图来自具体硬件与模型;可迁移的结论是数值表示、专用运算、工艺和并行性共同影响性能。
1.8.3 Pentium FDIV 与 NaN-boxing
课件第 83 页的 Pentium FDIV 案例说明:即使算术指令具有明确语义,硬件实现错误仍可能使少数输入得到错误结果。因此数值正确性也需要硬件与软件验证。
NaN-boxing 则利用某些 NaN 编码中的载荷位(Payload)保存类型标签或其他数据,用于动态语言的紧凑值表示。(第 85 页)这不是把所有 52 位任意填充就一定安全:必须保持合法的 NaN 编码,考虑静默/信号 NaN、指针表示及运行时约定。课件中的强制指针转换示意也不等于可直接移植的 C 实现。
1.9 练习与自检
课件第 63 页布置的教材题号为:1-55、58、59、65、71、75、78、80、81、82。课件未给出完整题干,应结合指定版教材完成。
可先检查以下结论是否能独立推导:
| 问题 | 核对结果 |
|---|---|
8 位 FEH 的无符号值和补码值 |
254、\(-2\) |
| 十进制 13 的整数编码与压缩 BCD | 0DH、13H |
小端内存 78 56 34 12 对应的双字 |
12345678H |
| binary32 为什么有 24 位精度 | 23 位小数加隐含首位 |
| \(2^{24}+1\) 为什么可能仍是 \(2^{24}\) | 间隔为 2,默认舍入选择偶数有效尾位 |
| 8088 的 8 位总线是否表示 8 位寄存器 | 否,寄存器为 16 位 |
1.10 术语表
| 中文术语 | English | 缩写 | 含义 |
|---|---|---|---|
| 存储程序 | Stored Program | — | 指令编码后存入内存 |
| 流水线 | Pipeline | — | 多条指令在不同处理阶段重叠执行 |
| 超标量 | Superscalar | — | 同周期处理多条指令的能力 |
| 乱序执行 | Out-of-Order Execution | OoOE | 按操作数就绪情况调度指令执行 |
| 微操作 | Micro-operation | μop | 处理器内部执行的较细粒度操作 |
| 单指令多数据 | Single Instruction, Multiple Data | SIMD | 对多个数据通道执行同类操作 |
| 超线程 | Hyper-Threading | HT | 单核心提供多个逻辑处理器 |
| 二进制编码十进制 | Binary-Coded Decimal | BCD | 用二进制逐位编码十进制数字 |
| 补码 | Two's Complement | — | 最高位具有负权重的有符号整数表示 |
| 小端序 | Little-endian | — | 低有效字节位于低地址 |
| 指数偏置 | Exponent Bias | — | 将实际指数映射为存储指数的偏移量 |
| 有效数 | Significand | — | 决定浮点数有效数字的部分 |
| 非规格化数 | Subnormal Number | — | 用于逐渐逼近零的小量级浮点数 |
| 渐进下溢 | Gradual Underflow | — | 借助非规格化数逐步降低精度 |
| 消去 | Cancellation | — | 相近数相减使有效数字大量丢失 |