跳转至

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 位单精度浮点数:

\[ [a_0,a_1,a_2,a_3]+[b_0,b_1,b_2,b_3] =[a_0+b_0,a_1+b_1,a_2+b_2,a_3+b_3]. \]
寄存器类别 宽度 可容纳的 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\) 的数,用各位数字与位权相乘后求和:

\[ (d_nd_{n-1}\cdots d_0.d_{-1}\cdots)_r =\sum_i d_i r^i,\qquad 0\le d_i<r. \]

例如:

\[ (11010)_2=1\cdot2^4+1\cdot2^3+1\cdot2^1=26. \]

二进制每位取 0 或 1;十六进制用 0–9、A–F,每位恰好对应 4 个二进制位。MASM 常用后缀 H、B 表示十六进制与二进制,例如 0FFH、1010B。(第 31、64–72 页)

1.3.2 十进制转二进制

整数部分采用反复除以 2、余数逆序排列;小数部分采用反复乘以 2、整数部分顺序排列。

课件例题:

\[ 725_{10}=512+128+64+16+4+1=(1011010101)_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

因此:

\[ 46.6875_{10}=(101110.1011)_2. \]

并非所有有限十进制小数都有有限二进制展开。例如课件的 \(0.678\) 在截取 12 个小数位时是 \((0.101011011001)_2\),只能写成近似相等。这是截断误差,不能称为数值溢出。

1.3.3 二进制、八进制、十六进制互换

从小数点向两侧分组:八进制每组 3 位,十六进制每组 4 位。整数部分在最左侧补零,小数部分在最右侧补零。

\[ (1001101.01101)_2=(0100\ 1101.0110\ 1000)_2=(4D.68)_{16}. \]

课件的跨进制例题:

\[ (635.177)_8=(110\ 011\ 101.001\ 111\ 111)_2=(19D.3F8)_{16}. \]

分组能够直接转换,是因为 \(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\),无符号值为:

\[ U=\sum_{i=0}^{n-1}b_i2^i,\qquad 0\le U\le2^n-1. \]

若按二进制补码(Two's Complement)解释,最高位权重变为负数:

\[ S=-b_{n-1}2^{n-1}+\sum_{i=0}^{n-2}b_i2^i, \qquad -2^{n-1}\le S\le2^{n-1}-1. \]

例如 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

补码让减法可以通过加法实现:

\[ A-B\equiv A+(2^n-B)\pmod {2^n}. \]

例如 \(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)的值为:

\[ x=(-1)^s\left(1+\frac{F}{2^t}\right)2^{E-\mathrm{bias}}. \]

最高位的 1 由规格化形式隐含,不保存到小数字段中,因此 binary32 虽然只存 23 位小数,却具有 24 位有效精度。

浮点符号与整数补码的负权最高位不同;指数也按偏置码存储。IEEE 浮点编码是固定宽度,课件第 50 页不能理解为“浮点格式必须是变长结构”。

1.7.2 编码例题:0.15625

\[ 0.15625=\frac{5}{32}=(0.00101)_2=(1.01)_2\times2^{-3}. \]

按 binary32 编码:

  1. 数值为正,\(s=0\)。
  2. 实际指数为 \(-3\),存储指数 \(E=-3+127=124=(01111100)_2\)。
  3. 去掉隐含的首位 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,其值为:

\[ x=(-1)^s\frac{F}{2^t}2^{1-\mathrm{bias}}. \]

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 位有效数字作类比:

\[ \begin{aligned} 123456.7+101.7654 &=(1.234567+0.001017654)\times10^5\\ &=1.235584654\times10^5\\ &\approx1.235585\times10^5. \end{aligned} \]

最终结果受可存储精度限制,和数学上的实数加法不同。

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):

\[ \sqrt{x+1}-\sqrt{x} =\frac{1}{\sqrt{x+1}+\sqrt{x}},\qquad x\ge0. \]

右侧避免了两个近似相等数相减,通常在这个场景下更稳定。课件用 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 — 相近数相减使有效数字大量丢失