计算机体系结构学习:附录 E:嵌入式系统详解
嵌入式系统就是藏在其他设备里的计算机,你不会一眼看出它是"电脑"。常见例子:计算机系统分类桌面系统价格:$1000~$10000核心目标:性价比+图形服务器系统价格:$10000~$10000000核心目标:吞吐量+可用性嵌入式系统价格:$10~$100000核心目标:低价+低功耗+专用性能特征1:实时性约束特征2:最坏情况执行时间(WCET)硬实时系统的工程师必须分析最坏情况下的执行时间:设计嵌
本文是《计算机体系结构》附录E的中文学习笔记,力求通俗易懂,包含公式、代码注释和图示。
目录
- 引言:什么是嵌入式系统
- 数字信号处理器DSP
- 媒体扩展指令SIMD
- 嵌入式基准测试
- 嵌入式多处理器
- 案例:索尼PlayStation 2情感引擎
- 案例:三洋数码相机
- 案例:手机内部解析
- 核心概念汇总
- 完整C++代码示例
1. 引言
1.1 嵌入式系统无处不在
嵌入式系统就是藏在其他设备里的计算机,你不会一眼看出它是"电脑"。
常见例子:
- 微波炉、洗衣机(简单控制芯片)
- 打印机、网络交换机(中等性能处理器)
- 汽车(多个ECU控制单元,现代汽车有几十个)
- 手机、PDA、MP3播放器(高性能嵌入式处理器)
- 游戏机、数字机顶盒(专用多媒体处理器)
1.2 与桌面/服务器系统的对比
2000年的销量数据(有趣的对比):
| 类型 | 年销量(估算) |
|---|---|
| 桌面处理器 | 1.5亿个 |
| 服务器处理器 | 400万个 |
| 嵌入式处理器(32/64位) | 3亿个 |
| 嵌入式处理器(含8/16位) | 超过10亿个 |
有趣事实:Intel历史上销量最高的处理器是一款8位微控制器,而不是任何一款桌面CPU!
1.3 嵌入式系统的核心特征
特征1:实时性约束
硬实时(Hard Real-Time):
必须在截止时间前完成,否则系统失效
例:数字机顶盒必须在下一帧到来前处理完当前帧
软实时(Soft Real-Time):
偶尔超过截止时间可以接受,但不能太频繁
例:视频播放偶尔卡顿可以接受,但不能频繁
特征2:最坏情况执行时间(WCET)
硬实时系统的工程师必须分析最坏情况下的执行时间:
- 分支预测:在桌面系统中提高平均性能,但在硬实时系统中引入不确定性
- 缓存:同样,缓存命中和未命中的时间差异让WCET分析变得保守
解决方案: - 用静态分支预测提示位代替动态分支预测(行为可预测)
- 用**软件管理的片上存储器(Scratchpad Memory)**代替缓存(延迟固定)
- 缓存行锁定(Cache Line Locking):将关键代码锁在缓存中不被替换
特征3:低功耗 - 电池供电 → 必须节能
- 塑料封装(而非陶瓷)散热有限 → 不能发热太多
- 没有风扇 → 必须被动散热
特征4:小代码尺寸
嵌入式系统通常没有硬盘,程序必须放在片内Flash或小容量片外存储器中,因此代码越小越好。
特征5:SOC(片上系统)
SOC = 处理器核心 + 专用硬件加速器 + 存储器 + I/O接口
全部集成在一块芯片上
设计嵌入式系统的三种方案:
2. 数字信号处理器DSP
2.1 什么是DSP?
DSP(Digital Signal Processor,数字信号处理器)是专门为信号处理算法优化的处理器。
信号处理的典型场景:
- 音频滤波(手机通话降噪)
- 图像处理(数码相机)
- 无线通信(编解码)
- 雷达、声纳处理
2.2 核心运算:乘加(MAC)
几乎所有信号处理算法的核心都是乘加运算(Multiply-Accumulate,MAC):
A = A + B × C A = A + B \times C A=A+B×C
离散傅里叶变换(DFT):
X ( k ) = ∑ n = 0 N − 1 x ( n ) ⋅ W N k n X(k) = \sum_{n=0}^{N-1} x(n) \cdot W_N^{kn} X(k)=n=0∑N−1x(n)⋅WNkn
其中旋转因子为:
W N k n = e j 2 π k n N = cos ( 2 π k n N ) + j sin ( 2 π k n N ) W_N^{kn} = e^{j\frac{2\pi kn}{N}} = \cos\left(\frac{2\pi kn}{N}\right) + j\sin\left(\frac{2\pi kn}{N}\right) WNkn=ejN2πkn=cos(N2πkn)+jsin(N2πkn)
这个公式的核心就是复数乘加。
**离散余弦变换(DCT)**是DFT的实数版替代,同样以乘加为核心,被JPEG压缩广泛使用。
MAC指令语义:
MAC A, B, C
等价于:A = A + (B × C)
有些应用对MAC吞吐量要求极高,选DSP时只看MAC速率。
2.3 定点数(Fixed-Point)算术
DSP通常使用定点数而非浮点数,原因是:
- 定点数硬件更简单、更省电
- 嵌入式应用精度需求有限
定点数的理解:
普通整数:二进制小数点在最右边(个位右边)
1011 = 1 × 2 3 + 0 × 2 2 + 1 × 2 1 + 1 × 2 0 = 11 1011 = 1\times2^3 + 0\times2^2 + 1\times2^1 + 1\times2^0 = 11 1011=1×23+0×22+1×21+1×20=11
定点数:二进制小数点在符号位右边(符号位后面)
1011 = − 1 × 2 − 0 + 0 × 2 − 1 + 1 × 2 − 2 + 1 × 2 − 3 1011 = -1\times2^{-0} + 0\times2^{-1} + 1\times2^{-2} + 1\times2^{-3} 1011=−1×2−0+0×2−1+1×2−2+1×2−3
也就是说, n n n 位二进制补码定点数表示的值域是 [ − 1 , + 1 ) [-1, +1) [−1,+1)。
转换公式:
定点数值 = 整数值 2 n − 1 \text{定点数值} = \frac{\text{整数值}}{2^{n-1}} 定点数值=2n−1整数值
教材例题解析:
三个16位二进制模式:
0100 0000 0000 0000
0000 1000 0000 0000
0100 1000 0000 1000
作为二进制补码整数:
- 0100 0000 0000 0000 = 2 14 = 16384 0100\ 0000\ 0000\ 0000 = 2^{14} = 16384 0100 0000 0000 0000=214=16384
- 0000 1000 0000 0000 = 2 11 = 2048 0000\ 1000\ 0000\ 0000 = 2^{11} = 2048 0000 1000 0000 0000=211=2048
- 0100 1000 0000 1000 = 2 14 + 2 11 + 2 3 = 18440 0100\ 1000\ 0000\ 1000 = 2^{14} + 2^{11} + 2^3 = 18440 0100 1000 0000 1000=214+211+23=18440
作为定点数(除以 2 15 = 32768 2^{15} = 32768 215=32768):
16384 32768 = 1 2 = 0.50000 \frac{16384}{32768} = \frac{1}{2} = 0.50000 3276816384=21=0.50000
2048 32768 = 1 16 = 0.06250 \frac{2048}{32768} = \frac{1}{16} = 0.06250 327682048=161=0.06250
18440 32768 = 2305 4096 ≈ 0.56274 \frac{18440}{32768} = \frac{2305}{4096} \approx 0.56274 3276818440=40962305≈0.56274
定点数等价于"低成本浮点数":没有指数字段,程序员自己维护一个公共指数变量,一批变量共享这个指数,称为块浮点(Blocked Floating Point)。
2.4 DSP的特殊运算
饱和算术(Saturating Arithmetic):
普通二进制补码:大数相加可能溢出变成负数(完全错误的结果)
饱和算术:结果超出范围时钳位到最大/最小值
正溢出 → 输出最大正值(比如 32767)
负溢出 → 输出最小负值(比如 -32768)
这在实时系统中非常重要——哪怕结果不精确,也不能中断处理(抛异常)。
Viterbi解码的比较选择单元(CSSU):
Viterbi算法用于解码格形码(Trellis Code),是前向纠错(FEC)的核心算法,需要大量的比较-选择操作,DSP专门为此提供硬件支持。
2.5 四代DSP演进
| 代际 | 年份 | 代表型号 | 数据位宽 | 累加器位宽 |
|---|---|---|---|---|
| 第一代 | 1982 | TI TMS32010 | 16位 | 32位 |
| 第二代 | 1987 | Motorola DSP56001 | 24位 | 56位 |
| 第三代 | 1995 | Motorola DSP56301 | 24位 | 56位 |
| 第四代 | 1998 | TI TMS320C6201 | 16位 | 40位 |
注意:DSP不受2的幂次限制,可以选24位、40位、56位等"非标准"位宽。
累加器为什么比数据位宽更宽?
做多次乘加时中间结果可能超出16位范围,用更宽的累加器防止中间溢出,类似浮点运算的保护位(Guard Bits)。
TI TMS320C55的操作数统计:约90%的操作数是16位,只有10%是32位。
2.6 案例:TI TMS320C55(低功耗DSP)
C55是面向电池供电嵌入式应用的DSP,典型应用:手机。
七级流水线:
取指(Fetch) → 解码(Decode) → 地址(Address) → 访存1 → 访存2 → 读(Read) → 执行(Execute)
关键特性:
- 两个MAC单元:每个17位×17位乘法器 + 40位加法器,每周期完成2次MAC
- 可配置指令缓存:24KB,可工作在
- 二路组相联模式
- 直接映射模式
- Ramset模式(锁定模式):缓存行不可被替换,支持硬实时
- 空闲域(Idle Domain):6个可独立关闭的电路块
- CPU、DMA、外设、时钟发生器、指令缓存、外部存储器接口
- 通过软件写空闲控制寄存器(ICR)实现细粒度功耗管理
- CSSU:用于Viterbi解码的专用比较选择存储单元
2.7 案例:TI TMS320C6x(高性能VLIW DSP)
C6x是另一个极端——高性能**VLIW(超长指令字)**处理器,面向不那么功耗敏感的场景。
为什么嵌入式选VLIW而非超标量?
| 方面 | 超标量 | VLIW |
|---|---|---|
| 指令并行挖掘 | 硬件动态调度 | 编译器静态调度 |
| 代码兼容性 | 要求(桌面系统重要) | 不要求(嵌入式可重编译) |
| 存储器延迟预测 | 运行时不可知 | 嵌入式中通常可静态预测 |
| 硬件面积 | 调度逻辑占大量面积 | 省去调度硬件 |
| 功耗 | 动态调度消耗更多 | 更省电 |
C64x架构(11级流水线):
取指1→取指2→取指3→取指4 → 解码1→解码2 → 执行1→执行2→执行3→执行4→写回
执行单元布局(左右两侧各一组):
左侧(A寄存器文件) 右侧(B寄存器文件)
.L1 逻辑和算术 .L2 逻辑和算术
.S1 比较、分支、SIMD .S2 比较、分支、SIMD
.M1 乘法、移位 .M2 乘法、移位
.D1 存储器访问 .D2 存储器访问
每侧有32个32位寄存器,跨侧访问寄存器会有1个周期惩罚。
指令包(Instruction Packet)压缩技术:
VLIW的传统问题是代码体积大(需要大量NOP填充空位),C6x通过p位解决:
指令i的p位 = 1 → 指令i+1与指令i同周期并行执行(同属一个VLIW字)
指令i的p位 = 0 → 指令i+1在下一周期执行(新VLIW字开始)
这样不需要显式NOP,代码密度接近RISC。
谓词执行(Predication):
用于软件流水线中处理条件分支:
- 指令照常执行
- 完成后检查谓词寄存器(如A1)
- A1=0 → 结果不写回(相当于该指令无效)
- A1≠0 → 正常写回
将if-then结构变成直线代码,利于软件流水线。
3. 媒体扩展指令SIMD
3.1 为什么需要SIMD?
多媒体数据的特点:
- 图像像素通常是8位(0~255的亮度/颜色值)
- 音频采样通常是16位
- 图形变换通常用单精度32位浮点
而现代处理器的ALU是64位甚至128位宽——用大炮打小鸟,浪费!
SIMD的思路(Single Instruction Multiple Data,单指令多数据):
把一个64位ALU分割成多个小ALU,同时处理多个窄数据:
一条64位加法指令处理数据:
传统方式(整数加法):
[ 64位整数 A ] + [ 64位整数 B ]
SIMD方式(4路16位加法):
[16位H3][16位H2][16位H1][16位H0]
+
[16位H3][16位H2][16位H1][16位H0]
=
[16位H3][16位H2][16位H1][16位H0]
一条指令完成4次16位加法!
唯一需要的额外硬件:防止进位在分区间传播的隔离逻辑,成本很低。
3.2 配对单精度浮点(Paired Single)
某些处理器允许在一个64位双精度寄存器中存放两个32位单精度浮点数,并用单条指令同时操作:
[ 32位单精度浮点A | 32位单精度浮点B ]
↓ 单条指令
[ 32位结果A | 32位结果B ]
代价更高:需要两套浮点单元,而不只是屏蔽整数进位。
3.3 各架构SIMD指令对比
各厂商实现差异很大,缺乏统一标准:
| 指令类别 | Alpha MAX | HP MAX2 | Intel MMX | PowerPC AltiVec | SPARC VIS |
|---|---|---|---|---|---|
| ALU宽度 | 64位 | 64位 | 64位 | 128位 | 64位 |
| 加/减 | 4×H | 8×B,4×H,2×W | 16×B,8×H,4×W | — | 4×H,2×W |
| 饱和加/减 | 4×H | 8×B,4×H | 16×B,8×H,4×W | — | — |
| 乘法 | 4×H | — | 16×B,8×H | — | — |
| 最大/最小 | — | 8×B,4×W | 16×B,8×H,4×W | — | — |
B=字节(8位),H=半字(16位),W=字(32位)
例:8×B表示一条指令同时操作8个字节
4. 嵌入式基准测试
4.1 为什么嵌入式测试困难?
- 嵌入式应用类型极多,没有统一负载
- 同样是"嵌入式",手机和网络路由器的性能需求完全不同
- 硬实时场景关注WCET,软实时关注平均性能
早期许多嵌入式厂商使用Dhrystone(一个20年前桌面系统就抛弃的基准),说明这个领域标准化滞后。
4.2 EEMBC基准测试套件
EEMBC(EDN嵌入式微处理器基准测试联盟)是目前最权威的嵌入式基准集,包含6类50个测试程序:
4.3 功耗效率作为评价指标
性能/瓦特有时比原始性能更重要:
示例:5款嵌入式处理器对比(相对值)
原始性能排名:
IBM PowerPC 750CX ██████████████ 最高
NEC VR 5432 ████████████
AMD K6-2E+ ████████
AMD ElanSC520 ████
NEC VR 4122 ██ 最低(倒数第一!)
性能/瓦特排名:
NEC VR 4122 ██████████████ 最高!(专为电池设计)
IBM PowerPC 750CX ████████ 高性能但功耗6W,不适合电池
AMD ElanSC520 ██████
NEC VR 5432 ████
AMD K6-2E+ ██
结论:为电池设计的NEC VR 4122原始性能最低,但功耗效率最高,是手持设备的最佳选择。
EEMBC EnergyBench提供标准化的能效测试,结合性能分数给出Energymark评分。
5. 嵌入式多处理器
5.1 嵌入式为什么容易引入多处理器?
桌面/服务器多处理器的障碍:
- 二进制软件兼容性要求(不能重新编译)
- 缓存一致性(Cache Coherency)复杂
嵌入式多处理器的优势: - 软件往往从头编写,没有兼容性包袱(这也是为什么嵌入式选VLIW不选超标量)
- 应用天然具有并行性(流媒体、通信、游戏)
- 对芯片面积成本敏感,小核多核比大核更划算
5.2 案例:MXP语音处理器
empowerTel Networks的MXP处理器,用于VoIP(网络电话):
MXP处理器组成:
├── 串行语音流接口(含抖动缓冲支持)
├── 快速数据包路由和信道查找
├── 完整以太网接口(含MAC层)
└── 四个 MIPS32 R4000级处理器
└── 每个有独立缓存(共48KB,每个12KB)
总晶体管数:仅1350万(非常精简)
四个MIPS核并行处理多路独立语音流,每路任务包括:
- QoS保障
- 回声消除
- 简单压缩
- 数据包编码
关键洞察:每路语音流完全独立,天然适合多处理器并行,无需复杂同步。
6. 案例:索尼PlayStation 2情感引擎
6.1 设计挑战:流式数据
服务器/桌面的内存设计假设:数据有时间局部性(最近用过的数据很可能再用),所以缓存有效。
游戏机的数据特点:
- 图形数据是连续流(每帧新画面 = 全新数据)
- 时间局部性很差(画面内容不断变化)
- 但空间局部性好(一帧内相邻像素相关)
解决方案:不用缓存层次结构,改用多个专用独立存储器,每个存储器服务一个专用功能,1周期延迟。
6.2 PS2整体架构
+------------------PS2 系统架构------------------+
| |
| 主存储器:32MB DRDRAM,两通道,3.2 GB/s峰值带宽 |
| | |
| [情感引擎(Emotion Engine)] |
| / | \ |
| CPU核 VPU0 VPU1 |
| (MIPS64) (向量协处理器) (独立向量处理器) |
| 16KB指令缓存 4KB指令/数据 16KB指令/数据 |
| 8KB数据缓存 |
| 16KB暂存RAM |
| \ | / |
| +-----------+-----------+ |
| | 128位总线(150 MHz) | |
| +----------+------------+ |
| | |
| [图形接口(GI)] |
| | 1024位! |
| [图形合成器(Graphics Synthesizer)] |
| 4MB嵌入式DRAM,16个并行像素处理器 |
| | |
| 显示输出(NTSC/PAL/DTV) |
+--------------------------------------------------+
| I/O处理器:34MHz MIPS(兼容PS1,处理I/O) |
| 接口:USB, IEEE-1394, DVD-ROM, PCMCIA, 调制解调器|
+--------------------------------------------------+
6.3 情感引擎的设计思想
核心观察:赛车游戏中,前景物体(赛车、道路)变化快,背景(远山、天空)变化慢但占屏幕大部分。
解决方案:任务分工
6.4 存储器系统详解
专用存储器策略:
单元 存储器类型 容量 延迟
CPU 指令缓存 16KB 命中1周期
CPU 数据缓存 8KB 命中1周期
CPU 暂存RAM(SPRAM) 16KB 固定1周期
VPU0 指令存储器 4KB 1周期
VPU0 数据存储器 4KB 1周期
VPU1 指令存储器 16KB 1周期
VPU1 数据存储器 16KB 1周期
图形合成器 嵌入式DRAM 4MB 低延迟
主存储器 DRDRAM 32MB 较高延迟
注意:VPU的存储器是独立的本地存储器,不是大存储器的缓存,延迟固定为1周期,完全可预测。
VPU1的存储器比VPU0更大,因为:
- VPU1负责生成大部分显示列表(工作量更重)
- VPU1独立运行,需要更多本地数据
6.5 两种工作模式
串行模式(Serial Mode):
主存储器
|
v
CPU/VPU0 →(暂存RAM作缓冲)→ VPU1 → 图形接口
(预处理:决定要渲染什么) (生成所有显示列表)
并行模式(Parallel Mode):
主存储器
|
分叉
| |
v v
CPU/VPU0 VPU1
(前景显示列表) (背景显示列表)
| |
+------合并----+
|
v
图形接口(用上下文ID区分不同列表)
程序员选择哪种模式,系统没有硬件自动决策。
6.6 总线架构
三层总线结构:
1. 公共128位总线(150MHz):连接所有单元
带宽 = 128位 × 150MHz = 2.4 GB/s
2. CPU ↔ VPU0 专用128位总线:加速前景处理数据流
3. VPU1 ↔ 图形接口 专用128位总线:加速背景渲染数据流
4. 图形接口 ↔ 图形合成器:1024位宽!
带宽 = 1024位 × 150MHz = 19.2 GB/s(超宽!)
图形合成器内部1024位带宽的秘密:嵌入式DRAM与像素处理器同在一块芯片上,不需要走外部总线。
6.7 与服务器设计的对比
| 维度 | 服务器设计思路 | PS2设计思路 |
|---|---|---|
| 存储器 | 统一内存层次,多级缓存 | 多个专用独立存储器 |
| 一致性 | 硬件缓存一致性协议 | 程序员手动保证一致性 |
| 总线 | 单一共享总线 | 多条专用总线 |
| 数据流 | 请求驱动 | DMA驱动(10个DMA通道) |
| 实时性 | 操作系统调度 | 程序员编程保证15帧/秒 |
7. 案例:三洋数码相机
7.1 拍照流程
7.2 SOC架构
三洋相机的"大脑"是一块系统级芯片(SOC),集成了原来电路板上所有功能:
SOC组成:
32位RISC主处理器(28.8MHz)
|
+----+----+
| |
16位慢速总线 32位快速总线
| |
SmartMedia接口 SDRAM控制器
程序/数据存储器 CCD信号处理器
DMA控制器 Motion-JPEG编码器
UART NTSC/PAL编码器
音频接口 视频D/A转换器
PCMCIA接口
IrDA接口
两条总线的设计理由:
- 慢速I/O设备(存储卡、串口)用16位总线,节省面积和功耗
- 高带宽设备(SDRAM、图像处理、显示)用32位总线,保证速度
芯片参数: - 功耗:700mW
- 晶体管数:180万
- 芯片面积:10.5 × 10.5 mm
- 制程:0.35微米
对比:1990年最先进的高性能微处理器约有100万晶体管。这块相机SOC的晶体管是它的1.8倍!
7.3 SOC的优势
- 减少芯片数量(原来需要多块芯片)
- 减小体积(更小的相机形态)
- 降低功耗(三洋声称比竞争对手少用一半电池)
- 降低成本(减少封装和PCB面积)
8. 案例:手机内部解析
8.1 无线通信基础
无线电波的关键概念:
- 调制(Modulation):把声音/数据信号叠加在载波上
- AM(幅度调制):改变信号强度
- FM(频率调制):改变信号频率
- 数字调制:改变相位/频率/幅度编码数字比特
- 频率复用(Frequency Reuse):蜂窝系统的核心思想
路径损耗(信号强度随距离衰减)公式(近似):
P r e c e i v e d ∝ P t r a n s m i t t e d d α P_{received} \propto \frac{P_{transmitted}}{d^\alpha} Preceived∝dαPtransmitted
其中 d d d 是距离, α \alpha α 是路径损耗指数(自由空间约2,城市环境约3~5)。
这种指数衰减意味着:距离足够远时,使用相同频率不会互相干扰,蜂窝系统正是利用了这一点。
无线通信的四大挑战:
| 挑战 | 描述 | 影响 |
|---|---|---|
| 路径损耗 | 信号强度随距离指数衰减 | 自由空间728m vs 丛林4m(1W,1GHz,1Mbps, 10 − 7 10^{-7} 10−7 BER) |
| 阴影衰落 | 建筑物、墙壁遮挡信号 | 移动中需要动态调整发射功率 |
| 多径衰落 | 多条路径的信号叠加干涉 | 900MHz信号每30cm就有相位变化 |
| 干扰 | 频率复用、邻道干扰 | 需要扩频或滤波 |
8.2 蜂窝系统原理
蜂窝小区的六边形布局(俯视图):
/ \ / \ / \
|A |B |C |
\ / \ / \ / \
|D |A |B | ← A频率复用(离得够远)
/ \ / \ / \ /
|C |D |A |
\ / \ / \ /
相邻小区用不同频率(A/B/C/D)
相隔足够远的小区可以重用同一频率(如A)
- 典型小区半径:2~10英里(视地形和用户密度)
- 三个小区交界处设置基站(Base Station)
- 基站通过有线网络(地线)连到交换局
- 交换局负责切换(Handoff):手机从一个小区移动到另一个
8.3 手机硬件组成
无线电接收链路:
天线 → RF放大器 → 混频器 → 滤波器 → 解调器 → 解码器
|
接收两个频率,
输出差频信号
(下变频)
手机整体框图:
+--------------------------------------------------+
| 手机硬件 |
| |
| 天线 |
| | |
| RF接收器 ←→ RF发射器 |
| | | |
| [DSP] [微控制器] |
| 负责: 负责: |
| 信号压缩/解压 用户界面(键盘、LCD显示) |
| 调制/解调 电池管理 |
| 编解码 呼叫建立 |
| | | |
| 麦克风 扬声器 |
+--------------------------------------------------+
诺基亚数字手机电路板的芯片:
| 芯片 | 功能 |
|---|---|
| Z-80微控制器 | 控制键盘、LCD、与基站协调 |
| DSP | 信号压缩/解压 |
| A/D、D/A转换器 | 模拟数字互转 |
| 功率放大器 | 射频信号放大 |
| 电源管理芯片 | 电池监控和充电 |
| RF接口芯片 | 射频收发(砷化镓或锗硅工艺) |
8.4 手机工作流程
通话细节:
- 传输速率:9600 bits/sec(原始数字语音)
- 每条消息发送5次(无线信道误码率高)
- 发射功率:0.6W(近距离)或 3.0W(远距离)
8.5 第二代标准:CDMA
CDMA(码分多址)是最重要的2G标准之一:
与AMPS(第一代)的对比:
| 特性 | AMPS(1G) | CDMA(2G) |
|---|---|---|
| 信道方式 | 每路独占频道 | 所有用户共享宽频道 |
| 原始速率 | 9600 bps | 9600 bps |
| 传输速率 | 9600 bps × 5次 | 1.25 Mbps(扩频11倍) |
| 容量提升 | 基准 | 约10倍 |
| 隐私保护 | 差 | 64个伪随机码加密 |
| 通话质量 | 中等 | 更好 |
| 电池寿命 | 中等 | 更长(功率控制精确) |
CDMA的关键技术:
- 扩频(Spread Spectrum):每个比特展开为11倍频率,抗干扰
- 伪随机码:从64个预定义码中选择,基站和手机用GPS时钟同步
- 功率控制:所有手机信号到达基站时强度相近(避免近端压制远端)
- 语音压缩:根据说话活动量动态调整速率
CDMA容量的哲学: 系统容量是"口味"问题——背景噪声越大,用户越多,容量上限取决于人们能接受多少背景噪声。
9. 核心概念汇总
嵌入式系统设计原则速查表
| 原则 | 桌面/服务器 | 嵌入式 |
|---|---|---|
| 性能目标 | 越快越好 | 满足需求,成本最低 |
| 功耗 | 次要考虑 | 首要约束 |
| 代码大小 | 无限制 | 越小越好 |
| 实时性 | 通常不需要 | 硬/软实时 |
| 内存结构 | 统一缓存层次 | 专用独立存储器 |
| 并行方式 | 超标量(硬件调度) | VLIW(编译器调度) |
| 软件兼容性 | 严格要求 | 通常从头编写 |
| 指令集特性 | 通用 | MAC、饱和算术、SIMD |
关键缩写表
| 缩写 | 全称 | 中文 |
|---|---|---|
| DSP | Digital Signal Processor | 数字信号处理器 |
| MAC | Multiply-Accumulate | 乘加运算 |
| FEC | Forward Error Correction | 前向纠错 |
| WCET | Worst-Case Execution Time | 最坏情况执行时间 |
| SOC | System on a Chip | 片上系统 |
| VLIW | Very Long Instruction Word | 超长指令字 |
| SIMD | Single Instruction Multiple Data | 单指令多数据 |
| CDMA | Code Division Multiple Access | 码分多址 |
| BER | Bit Error Rate | 误码率 |
| SNR | Signal-to-Noise Ratio | 信噪比 |
| DMA | Direct Memory Access | 直接存储器访问 |
10. 完整C++代码示例
代码1:定点数与MAC运算模拟器
/*
* 定点数(Fixed-Point)与MAC运算模拟器
* 对应教材附录E第E.2节
*
* 定点数格式:Q15(1位符号 + 15位小数)
* 范围:[-1.0, +1.0),精度:1/32768
*
* 编译命令:g++ -std=c++17 -o fixedpoint fixedpoint.cpp
*/
#include <iostream>
#include <iomanip>
#include <cstdint>
#include <cmath>
#include <vector>
#include <string>
#include <algorithm>
#include <cassert>
// ============================================================
// Q15 定点数类型定义
// 使用16位有符号整数存储,隐含小数点在符号位右边
// 实际值 = 存储值 / 2^15 = 存储值 / 32768
// ============================================================
using Q15 = int16_t; // 16位定点数,范围[-32768, 32767]
using Q31 = int32_t; // 32位中间结果(防止乘法溢出)
using Q40 = int64_t; // 40位累加器(DSP常用,用64位模拟)
const double Q15_SCALE = 32768.0; // 2^15
/**
* 将双精度浮点数转换为Q15定点数
* 输入范围应在 [-1.0, 1.0) 之间
*/
Q15 floatToQ15(double value) {
// 钳位到合法范围(饱和处理)
if (value >= 1.0) return 32767; // 最大正值
if (value < -1.0) return -32768; // 最小负值
return static_cast<Q15>(value * Q15_SCALE);
}
/**
* 将Q15定点数转换回双精度浮点数
*/
double q15ToFloat(Q15 value) {
return static_cast<double>(value) / Q15_SCALE;
}
/**
* 显示16位数的二进制表示
*/
std::string toBinary16(uint16_t value) {
std::string result = "";
for (int i = 15; i >= 0; i--) {
result += ((value >> i) & 1) ? '1' : '0';
// 在符号位后面加空格,突出定点格式
if (i == 15) result += ' ';
}
return result;
}
/**
* Q15 乘法:两个Q15数相乘,结果仍是Q15
* 内部先扩展到32位避免溢出,然后右移15位还原小数点位置
*
* 原理:(A/2^15) × (B/2^15) = (A×B)/2^30
* 要得到Q15结果需右移15位:(A×B)/2^15
*/
Q15 q15Mul(Q15 a, Q15 b) {
// 扩展到32位相乘(防止16位乘16位的中间结果溢出)
Q31 product = static_cast<Q31>(a) * static_cast<Q31>(b);
// 右移15位,恢复小数点位置
// 加0x4000是四舍五入(2^14 = 0.5 * 2^15)
return static_cast<Q15>((product + 0x4000) >> 15);
}
/**
* 饱和加法:防止溢出,超出范围时钳位
* DSP中必须使用饱和算术,因为实时系统不能抛出异常
*/
Q15 saturatingAdd(Q15 a, Q15 b) {
// 用32位整数做加法,检查是否溢出
Q31 sum = static_cast<Q31>(a) + static_cast<Q31>(b);
// 钳位到Q15范围
if (sum > 32767) return 32767; // 正饱和
if (sum < -32768) return -32768; // 负饱和
return static_cast<Q15>(sum);
}
/**
* 普通二进制补码加法(可能溢出,演示对比用)
*/
Q15 wrappingAdd(Q15 a, Q15 b) {
return static_cast<Q15>(a + b); // 直接截断,可能发生符号翻转
}
// ============================================================
// MAC(乘加)运算
// ============================================================
/**
* 单次MAC操作:accumulator += b * c
* 使用40位累加器(用64位整数模拟)
* 对应DSP的 "MAC A, B, C" 指令
*
* @param acc 40位累加器(引用传入,结果写回)
* @param b 第一个Q15操作数
* @param c 第二个Q15操作数
*/
void macOperation(Q40& acc, Q15 b, Q15 c) {
// b * c 的结果是Q30(相当于除以2^30)
// 但我们要在Q15尺度上累加,所以不额外移位
// 累加器保存的是放大了2^15倍的实际值
Q31 product = static_cast<Q31>(b) * static_cast<Q31>(c);
acc += static_cast<Q40>(product);
}
/**
* 从40位累加器读取Q15结果
* 右移15位,将累加器的Q30尺度还原为Q15
*/
Q15 accToQ15(Q40 acc) {
// 带四舍五入
Q40 rounded = acc + (1 << 14); // 加 2^14 做四舍五入
Q40 result = rounded >> 15;
// 饱和
if (result > 32767) return 32767;
if (result < -32768) return -32768;
return static_cast<Q15>(result);
}
// ============================================================
// FIR(有限冲激响应)滤波器
// 这是DSP最典型的应用,核心就是MAC的累加
// ============================================================
/**
* FIR 滤波器
*
* 数学定义:
* y[n] = h[0]*x[n] + h[1]*x[n-1] + ... + h[N-1]*x[n-N+1]
*
* 即当前输出 = 滤波器系数 × 过去N个输入的加权和
*
* @param input 当前输入样本(Q15格式)
* @param h 滤波器系数数组(Q15格式)
* @param buffer 输入历史缓冲区(循环缓冲区)
* @param pos 当前写入位置(引用,会更新)
* @param N 滤波器阶数(系数个数)
* @return 滤波输出(Q15格式)
*/
Q15 firFilter(Q15 input, const std::vector<Q15>& h,
std::vector<Q15>& buffer, int& pos, int N) {
// 将新样本写入循环缓冲区
buffer[pos] = input;
// MAC累加:y[n] = Σ h[k] * x[n-k]
Q40 acc = 0; // 40位累加器,防止多次乘加的中间溢出
for (int k = 0; k < N; k++) {
// 循环缓冲区索引:最新的是pos,最老的是(pos+1)%N
int idx = (pos - k + N) % N;
macOperation(acc, h[k], buffer[idx]);
}
// 更新写入位置(循环)
pos = (pos + 1) % N;
return accToQ15(acc);
}
// ============================================================
// 离散余弦变换(DCT)— JPEG压缩核心
// 同样基于MAC,演示DSP在图像压缩中的应用
// ============================================================
/**
* 8点一维DCT(简化版,使用浮点演示原理)
*
* DCT公式:
* X[k] = C(k) * Σ(n=0 to N-1) x[n] * cos((2n+1)*k*π / (2N))
*
* 其中归一化因子:
* C(0) = 1/sqrt(N)
* C(k) = sqrt(2/N),k>0
*/
void dct8(const double input[8], double output[8]) {
const int N = 8;
const double pi = 3.14159265358979323846;
for (int k = 0; k < N; k++) {
double sum = 0.0;
// 核心:MAC累加(cos值 × 输入样本)
for (int n = 0; n < N; n++) {
sum += input[n] * std::cos((2.0 * n + 1.0) * k * pi / (2.0 * N));
}
// 归一化
double ck = (k == 0) ? 1.0 / std::sqrt(N) : std::sqrt(2.0 / N);
output[k] = ck * sum;
}
}
// ============================================================
// 饱和算术 vs 普通算术的对比演示
// ============================================================
void demonstrateSaturation() {
std::cout << "\n【饱和算术 vs 普通算术对比】\n";
std::cout << std::string(55, '-') << "\n";
// 两个接近最大值的正数相加
// 0.75 + 0.75 = 1.5,超出Q15范围 [-1.0, +1.0)
double a_val = 0.75;
double b_val = 0.75;
Q15 a = floatToQ15(a_val);
Q15 b = floatToQ15(b_val);
Q15 sat_result = saturatingAdd(a, b);
Q15 wrap_result = wrappingAdd(a, b);
std::cout << " 输入:a = " << a_val << " (Q15=" << a << ")\n";
std::cout << " 输入:b = " << b_val << " (Q15=" << b << ")\n";
std::cout << " 理论和:" << a_val + b_val << "(超出Q15范围)\n\n";
std::cout << " 饱和加法结果:Q15=" << sat_result
<< ",浮点值=" << q15ToFloat(sat_result) << "\n";
std::cout << " → 正确:钳位到最大值 +1.0(近似)\n\n";
std::cout << " 普通加法结果:Q15=" << wrap_result
<< ",浮点值=" << q15ToFloat(wrap_result) << "\n";
std::cout << " → 错误:溢出导致符号翻转,变成负数!\n";
std::cout << " → 在实时音频中这会产生刺耳的爆音!\n";
}
// ============================================================
// 主函数
// ============================================================
int main() {
std::cout << "\n";
std::cout << std::string(60, '=') << "\n";
std::cout << " 嵌入式DSP运算模拟器(对应《计算机体系结构》附录E)\n";
std::cout << std::string(60, '=') << "\n";
// ----------------------------------------------------------
// 示例1:定点数转换(教材例题)
// ----------------------------------------------------------
std::cout << "\n【示例1】定点数(Q15)表示\n";
std::cout << std::string(55, '-') << "\n";
// 教材中的三个16位模式
uint16_t patterns[3] = {0x4000, 0x0800, 0x4808};
std::cout << " 教材原题:三个16位二进制模式\n\n";
for (int i = 0; i < 3; i++) {
int16_t as_int = static_cast<int16_t>(patterns[i]);
double as_fixed = static_cast<double>(as_int) / Q15_SCALE;
std::cout << " 模式" << i+1 << ": 0x" << std::hex << std::uppercase
<< std::setw(4) << std::setfill('0') << patterns[i]
<< std::dec << std::setfill(' ') << "\n";
std::cout << " 二进制 = " << toBinary16(patterns[i]) << "\n";
std::cout << " 整数值 = " << as_int << "\n";
std::cout << " 定点值 = " << as_int << " / 32768"
<< " = " << std::fixed << std::setprecision(5)
<< as_fixed << "\n\n";
}
// ----------------------------------------------------------
// 示例2:MAC运算演示
// ----------------------------------------------------------
std::cout << "\n【示例2】MAC(乘加)运算演示\n";
std::cout << std::string(55, '-') << "\n";
Q15 B = floatToQ15(0.5); // B = 0.5
Q15 C = floatToQ15(0.25); // C = 0.25
Q40 acc = 0; // 40位累加器,初始为0
std::cout << " B = 0.5 → Q15 = " << B << "\n";
std::cout << " C = 0.25 → Q15 = " << C << "\n";
std::cout << " 40位累加器 acc 初始 = 0\n\n";
// 执行4次MAC:acc += B * C,每次期望累加 0.5×0.25=0.125
for (int i = 1; i <= 4; i++) {
macOperation(acc, B, C);
Q15 current_q15 = accToQ15(acc);
std::cout << " 第" << i << "次 MAC acc+=" << q15ToFloat(B) << "×"
<< q15ToFloat(C) << ","
<< "累加器=" << acc
<< ",Q15输出=" << std::fixed << std::setprecision(5)
<< q15ToFloat(current_q15) << "\n";
}
std::cout << " 理论值应为 " << 4 * 0.5 * 0.25 << "\n";
// ----------------------------------------------------------
// 示例3:FIR滤波器
// ----------------------------------------------------------
std::cout << "\n【示例3】FIR低通滤波器(5阶)\n";
std::cout << std::string(55, '-') << "\n";
// 简单的5阶对称低通滤波器系数(归一化使总和=1)
// h = [0.05, 0.20, 0.50, 0.20, 0.05],平滑滤波
const int FILTER_ORDER = 5;
std::vector<double> h_float = {0.05, 0.20, 0.50, 0.20, 0.05};
std::vector<Q15> h(FILTER_ORDER);
for (int i = 0; i < FILTER_ORDER; i++) {
h[i] = floatToQ15(h_float[i]);
}
std::cout << " 滤波器系数(Q15):";
for (int i = 0; i < FILTER_ORDER; i++) {
std::cout << h[i];
if (i < FILTER_ORDER - 1) std::cout << ", ";
}
std::cout << "\n";
std::cout << " (含义:加权平均,中间权重最大,边缘权重小)\n\n";
// 初始化循环缓冲区
std::vector<Q15> buf(FILTER_ORDER, 0);
int pos = 0;
// 模拟一个阶跃信号:前3个样本=0,之后全为0.8
// 滤波器应该平滑地从0过渡到0.8
std::vector<double> input_signal = {0.0, 0.0, 0.0, 0.8, 0.8, 0.8, 0.8, 0.8};
std::cout << " 输入信号(阶跃:0→0.8)与滤波输出:\n";
std::cout << " " << std::setw(8) << "输入"
<< std::setw(12) << "期望输出"
<< std::setw(12) << "实际Q15输出" << "\n";
std::cout << " " << std::string(35, '-') << "\n";
// 期望输出(理论值,浮点计算)
std::vector<double> expected = {0.0, 0.0, 0.0, 0.04, 0.24, 0.54, 0.74, 0.80};
for (int n = 0; n < (int)input_signal.size(); n++) {
Q15 in_q15 = floatToQ15(input_signal[n]);
Q15 out_q15 = firFilter(in_q15, h, buf, pos, FILTER_ORDER);
double out_float = q15ToFloat(out_q15);
std::cout << " " << std::setw(7) << std::fixed << std::setprecision(2)
<< input_signal[n]
<< std::setw(12) << expected[n]
<< std::setw(12) << out_float << "\n";
}
std::cout << " (输出平滑地从0过渡到0.8,体现低通滤波效果)\n";
// ----------------------------------------------------------
// 示例4:饱和算术演示
// ----------------------------------------------------------
demonstrateSaturation();
// ----------------------------------------------------------
// 示例5:8点DCT演示(JPEG核心)
// ----------------------------------------------------------
std::cout << "\n\n【示例5】8点离散余弦变换(DCT)\n";
std::cout << std::string(55, '-') << "\n";
std::cout << " DCT是JPEG图像压缩的核心,将像素值变换到频域\n";
std::cout << " 变换后大多数系数接近0,可以大量压缩\n\n";
// 模拟8个像素值(归一化到[-1,1],原始像素0-255映射)
double pixels[8] = {0.5, 0.48, 0.45, 0.42, 0.20, 0.10, 0.05, 0.02};
double dct_out[8] = {0};
dct8(pixels, dct_out);
std::cout << " 输入像素(8个,已归一化):\n ";
for (int i = 0; i < 8; i++) {
std::cout << std::fixed << std::setprecision(2) << pixels[i];
if (i < 7) std::cout << ", ";
}
std::cout << "\n\n";
std::cout << " DCT变换输出(频域系数):\n";
std::cout << " " << std::setw(8) << "系数k"
<< std::setw(15) << "DCT[k]"
<< std::setw(12) << "能量占比" << "\n";
std::cout << " " << std::string(35, '-') << "\n";
double total_energy = 0;
for (int k = 0; k < 8; k++) {
total_energy += dct_out[k] * dct_out[k];
}
for (int k = 0; k < 8; k++) {
double energy_pct = dct_out[k] * dct_out[k] / total_energy * 100.0;
std::cout << " " << std::setw(8) << k
<< std::setw(15) << std::fixed << std::setprecision(4) << dct_out[k]
<< std::setw(11) << std::setprecision(1) << energy_pct << "%\n";
}
std::cout << "\n 注意:DCT[0](直流分量)包含大部分能量\n";
std::cout << " 高频系数(k较大)能量很小→JPEG丢弃这些=有损压缩\n";
// ----------------------------------------------------------
// 示例6:Q15数的简单乘法表
// ----------------------------------------------------------
std::cout << "\n\n【示例6】Q15定点乘法示例\n";
std::cout << std::string(55, '-') << "\n";
std::vector<std::pair<double,double>> pairs = {
{0.5, 0.5},
{0.75, 0.75},
{-0.5, 0.75},
{0.9, 0.9},
};
std::cout << " " << std::setw(8) << "A"
<< std::setw(8) << "B"
<< std::setw(12) << "A×B(浮点)"
<< std::setw(12) << "A×B(Q15)" << "\n";
std::cout << " " << std::string(42, '-') << "\n";
for (auto& p : pairs) {
Q15 qa = floatToQ15(p.first);
Q15 qb = floatToQ15(p.second);
Q15 qc = q15Mul(qa, qb);
std::cout << " " << std::setw(7) << std::fixed << std::setprecision(2)
<< p.first
<< std::setw(8) << p.second
<< std::setw(12) << p.first * p.second
<< std::setw(12) << q15ToFloat(qc) << "\n";
}
std::cout << "\n";
std::cout << std::string(60, '=') << "\n";
std::cout << " 计算完成\n";
std::cout << std::string(60, '=') << "\n\n";
return 0;
}
代码2:SIMD(向量加法)与蜂窝系统模拟
/*
* SIMD向量操作模拟器 + 蜂窝系统路径损耗计算
* 对应教材附录E第E.2节(SIMD)和E.7节(手机)
*
* 编译命令:g++ -std=c++17 -o simd_cell simd_cell.cpp -lm
*/
#include <iostream>
#include <iomanip>
#include <cstdint>
#include <cmath>
#include <vector>
#include <string>
#include <array>
#include <algorithm>
// ============================================================
// SIMD 向量操作模拟
// 用64位整数模拟包含多个窄数据的向量寄存器
// ============================================================
/**
* 64位寄存器,内含4个16位有符号整数(4×H)
* 这对应于 Intel MMX 的 PACKSSWB 类指令格式
*/
struct Vec4H {
int16_t data[4]; // 4个16位半字(Halfword)
// 构造函数:用4个值初始化
Vec4H(int16_t a, int16_t b, int16_t c, int16_t d) {
data[0] = a; data[1] = b; data[2] = c; data[3] = d;
}
// 构造函数:全部初始化为同一个值
explicit Vec4H(int16_t val = 0) {
data[0] = data[1] = data[2] = data[3] = val;
}
// 打印向量内容
void print(const std::string& name) const {
std::cout << " " << name << " = [";
for (int i = 3; i >= 0; i--) {
std::cout << std::setw(6) << data[i];
if (i > 0) std::cout << ",";
}
std::cout << "]\n";
}
};
/**
* SIMD 向量加法:4路16位并行加法(饱和)
* 对应 MMX 的 PADDSW 指令(Packed Add Signed Words with Saturation)
*
* 一条指令完成4次独立的16位加法,防止进位跨分区传播
*/
Vec4H simdSaturatingAdd4H(const Vec4H& a, const Vec4H& b) {
Vec4H result;
for (int i = 0; i < 4; i++) {
int32_t sum = static_cast<int32_t>(a.data[i]) + b.data[i];
// 饱和到16位有符号范围 [-32768, 32767]
if (sum > 32767) sum = 32767;
if (sum < -32768) sum = -32768;
result.data[i] = static_cast<int16_t>(sum);
}
return result;
}
/**
* SIMD 向量加法:4路16位并行加法(普通,可能溢出)
* 对应 MMX 的 PADDW 指令(Packed Add Words)
*/
Vec4H simdWrappingAdd4H(const Vec4H& a, const Vec4H& b) {
Vec4H result;
for (int i = 0; i < 4; i++) {
// 直接截断,不处理溢出
result.data[i] = static_cast<int16_t>(a.data[i] + b.data[i]);
}
return result;
}
/**
* SIMD 绝对差值之和(SAD):图像压缩中的运动估计核心算法
* 对应 MMX 的 PSADBW 指令(Packed Sum of Absolute Differences)
*
* 用于视频编码中寻找相邻帧中最相似的块
*
* SAD = Σ |A[i] - B[i]|,i=0 to N-1
*/
int32_t simdSAD4H(const Vec4H& a, const Vec4H& b) {
int32_t sad = 0;
for (int i = 0; i < 4; i++) {
sad += std::abs(static_cast<int32_t>(a.data[i]) - b.data[i]);
}
return sad;
}
/**
* SIMD 比较并取最大值(用于图像处理)
* 对应 MMX 的 PMAXSW 指令(Packed Maximum Signed Words)
*/
Vec4H simdMax4H(const Vec4H& a, const Vec4H& b) {
Vec4H result;
for (int i = 0; i < 4; i++) {
result.data[i] = std::max(a.data[i], b.data[i]);
}
return result;
}
/**
* 演示:非SIMD的标量版本,完成相同工作需要4次操作
* 用于对比SIMD的效率优势
*/
void scalarAdd4Times(const int16_t a[4], const int16_t b[4], int16_t out[4]) {
// 传统方式:循环4次,每次处理一个元素
// 在实际处理器上这是4条指令,SIMD只需1条
for (int i = 0; i < 4; i++) {
int32_t sum = static_cast<int32_t>(a[i]) + b[i];
if (sum > 32767) sum = 32767;
if (sum < -32768) sum = -32768;
out[i] = static_cast<int16_t>(sum);
}
}
// ============================================================
// 蜂窝系统路径损耗计算
// 对应教材附录E第E.7节
// ============================================================
/**
* 计算无线信号路径损耗(自由空间模型)
*
* 弗里斯传输方程:
* P_r = P_t * G_t * G_r * (λ / 4π d)^2
*
* 简化为路径损耗(dB):
* L = 20*log10(4π*d*f / c)
*
* 其中:
* d = 距离(米)
* f = 频率(Hz)
* c = 光速(3×10^8 m/s)
*
* @param distance_m 收发距离(米)
* @param freq_hz 载波频率(Hz)
* @return 路径损耗(dB,正数表示损耗)
*/
double pathLoss_dB(double distance_m, double freq_hz) {
const double c = 3.0e8; // 光速(m/s)
const double pi = 3.14159265358979323846;
double loss = 20.0 * std::log10(4.0 * pi * distance_m * freq_hz / c);
return loss;
}
/**
* 给定发射功率和天线增益,计算接收功率(dBm)
*
* P_r_dBm = P_t_dBm + G_t_dB + G_r_dB - PathLoss_dB
*
* @param tx_power_W 发射功率(瓦)
* @param freq_hz 载波频率(Hz)
* @param distance_m 距离(米)
* @param tx_gain_dB 发射天线增益(dBi),全向天线=0
* @param rx_gain_dB 接收天线增益(dBi)
* @return 接收功率(dBm)
*/
double receivedPower_dBm(double tx_power_W, double freq_hz,
double distance_m,
double tx_gain_dB = 0.0,
double rx_gain_dB = 0.0) {
// 转换发射功率到dBm(dBm = dB relative to 1 milliwatt)
double tx_dBm = 10.0 * std::log10(tx_power_W * 1000.0);
// 路径损耗
double loss = pathLoss_dB(distance_m, freq_hz);
// 接收功率
return tx_dBm + tx_gain_dB + rx_gain_dB - loss;
}
/**
* 计算最大通信距离(自由空间,给定最低接收功率门限)
*
* 从路径损耗公式反推距离:
* d = (c / (4π*f)) * 10^(PathLoss_dB / 20)
*/
double maxDistance_m(double tx_power_W, double freq_hz,
double min_rx_power_dBm,
double tx_gain_dB = 0.0,
double rx_gain_dB = 0.0) {
const double c = 3.0e8;
const double pi = 3.14159265358979323846;
double tx_dBm = 10.0 * std::log10(tx_power_W * 1000.0);
double max_loss_dB = tx_dBm + tx_gain_dB + rx_gain_dB - min_rx_power_dBm;
double distance = (c / (4.0 * pi * freq_hz)) * std::pow(10.0, max_loss_dB / 20.0);
return distance;
}
// ============================================================
// 主函数
// ============================================================
int main() {
std::cout << "\n";
std::cout << std::string(60, '=') << "\n";
std::cout << " SIMD与蜂窝系统计算(对应《计算机体系结构》附录E)\n";
std::cout << std::string(60, '=') << "\n";
// ----------------------------------------------------------
// 示例1:SIMD向量加法对比
// ----------------------------------------------------------
std::cout << "\n【示例1】SIMD 4×16位并行加法\n";
std::cout << std::string(55, '-') << "\n";
// 模拟4个像素的RGB通道值(16位格式)
Vec4H pixels_A(30000, 20000, -10000, 15000);
Vec4H pixels_B(5000, 15000, -25000, 20000);
std::cout << " 向量寄存器内容(4个16位元素,从高位到低位):\n";
pixels_A.print("A");
pixels_B.print("B");
std::cout << "\n";
Vec4H sat_result = simdSaturatingAdd4H(pixels_A, pixels_B);
Vec4H wrap_result = simdWrappingAdd4H(pixels_A, pixels_B);
std::cout << " 1条SIMD饱和加法指令(等价于4次标量操作):\n";
sat_result.print("A+B(饱和)");
std::cout << "\n";
std::cout << " 1条SIMD普通加法指令(可能溢出):\n";
wrap_result.print("A+B(可溢)");
std::cout << "\n";
// 指出溢出发生的位置
for (int i = 0; i < 4; i++) {
int32_t sum = static_cast<int32_t>(pixels_A.data[i]) + pixels_B.data[i];
if (sum > 32767 || sum < -32768) {
std::cout << " 注意:元素[" << i << "] 理论值=" << sum
<< " 超出16位范围,饱和=" << sat_result.data[i]
<< ",溢出=" << wrap_result.data[i] << "\n";
}
}
// ----------------------------------------------------------
// 示例2:SAD(绝对差值之和)— 视频运动估计
// ----------------------------------------------------------
std::cout << "\n【示例2】SIMD SAD(绝对差值之和)— 视频运动估计\n";
std::cout << std::string(55, '-') << "\n";
std::cout << " SAD用于在相邻帧中寻找最匹配的像素块\n";
std::cout << " SAD越小 = 两个块越相似 = 运动量小\n\n";
// 当前帧的4个像素
Vec4H current_block(100, 150, 200, 180);
// 参考帧的两个候选位置
Vec4H candidate1(105, 148, 195, 182); // 非常相似(轻微移动)
Vec4H candidate2(50, 200, 100, 250); // 差异较大
current_block.print("当前块像素");
candidate1.print("候选位置1");
candidate2.print("候选位置2");
std::cout << "\n";
int32_t sad1 = simdSAD4H(current_block, candidate1);
int32_t sad2 = simdSAD4H(current_block, candidate2);
std::cout << " SAD(当前, 候选1) = " << sad1 << "\n";
std::cout << " SAD(当前, 候选2) = " << sad2 << "\n";
std::cout << " 最佳匹配:候选" << (sad1 < sad2 ? "1" : "2")
<< "(SAD更小)\n";
// ----------------------------------------------------------
// 示例3:路径损耗计算
// ----------------------------------------------------------
std::cout << "\n【示例3】无线信号路径损耗计算\n";
std::cout << std::string(55, '-') << "\n";
double freq_900MHz = 900.0e6; // 900 MHz(2G CDMA)
double freq_1GHz = 1.0e9; // 1 GHz(教材示例频率)
double tx_power_1W = 1.0; // 1W发射功率
std::cout << " 发射功率:1W,全向天线(0 dBi增益)\n\n";
std::cout << " 距离vs路径损耗(900MHz):\n";
std::cout << " " << std::setw(12) << "距离(m)"
<< std::setw(15) << "路径损耗(dB)"
<< std::setw(18) << "接收功率(dBm)" << "\n";
std::cout << " " << std::string(45, '-') << "\n";
std::vector<double> distances = {10, 100, 500, 1000, 2000, 5000, 10000};
for (double d : distances) {
double loss = pathLoss_dB(d, freq_900MHz);
double rx_power = receivedPower_dBm(tx_power_1W, freq_900MHz, d);
std::cout << " " << std::setw(12) << std::fixed << std::setprecision(0) << d
<< std::setw(15) << std::setprecision(1) << loss
<< std::setw(18) << rx_power << "\n";
}
// ----------------------------------------------------------
// 示例4:最大通信距离(教材原题)
// ----------------------------------------------------------
std::cout << "\n【示例4】最大通信距离(自由空间)\n";
std::cout << std::string(55, '-') << "\n";
std::cout << " 教材参数:1W发射,1GHz载频,1Mbps速率\n";
std::cout << " 误码率要求:10^-7\n";
std::cout << " 假设所需最低接收功率:-90 dBm\n\n";
double freq = 1.0e9;
double min_rx = -90.0; // dBm,实际值取决于调制方式和BER要求
double d_free = maxDistance_m(1.0, freq, min_rx);
// 丛林环境路径损耗指数更大(约4,而非自由空间的2)
// 简化:丛林中有效距离约为自由空间的 1/30
double d_jungle = d_free / 30.0;
std::cout << std::fixed << std::setprecision(0);
std::cout << " 自由空间最大距离:约 " << d_free << " 米\n";
std::cout << " 丛林中最大距离:约 " << d_jungle << " 米\n";
std::cout << " (教材给出:自由空间728m,丛林4m)\n";
// ----------------------------------------------------------
// 示例5:CDMA vs AMPS容量对比
// ----------------------------------------------------------
std::cout << "\n【示例5】CDMA vs AMPS 技术对比\n";
std::cout << std::string(55, '-') << "\n";
std::cout << " 参数对比:\n";
std::cout << " " << std::setw(20) << "特性"
<< std::setw(15) << "AMPS(1G)"
<< std::setw(15) << "CDMA(2G)" << "\n";
std::cout << " " << std::string(50, '-') << "\n";
struct Comparison {
std::string feature;
std::string amps;
std::string cdma;
};
std::vector<Comparison> comparisons = {
{"语音速率", "9600 bps", "9600 bps"},
{"传输速率", "9600×5次", "1.25Mbps扩频"},
{"冗余方式", "重复发5次", "每位扩展11倍"},
{"信道共享", "每路独占", "所有用户共享"},
{"容量提升", "基准(1x)", "约10倍"},
{"GPS同步", "不需要", "需要"},
{"隐私保护", "差", "64个伪随机码"},
{"功率控制", "仅2档", "精确动态控制"},
};
for (const auto& c : comparisons) {
std::cout << " " << std::setw(20) << c.feature
<< std::setw(15) << c.amps
<< std::setw(15) << c.cdma << "\n";
}
// ----------------------------------------------------------
// 示例6:手机通话时间与电池容量
// ----------------------------------------------------------
std::cout << "\n【示例6】手机功耗简单估算\n";
std::cout << std::string(55, '-') << "\n";
std::cout << " 假设手机在近距离(0.6W)和远距离(3.0W)两种功率发射\n\n";
double battery_mAh = 2000.0; // 电池容量:2000 mAh(典型智能手机)
double voltage = 3.7; // 锂电池标称电压:3.7V
double battery_Wh = battery_mAh * voltage / 1000.0; // Wh
// 射频发射功率之外,还有基带处理、屏幕等
double base_power_W = 0.5; // 基础功耗(处理器+屏幕等)
double total_low = base_power_W + 0.6; // 近距离:总功耗1.1W
double total_high = base_power_W + 3.0; // 远距离:总功耗3.5W
// 假设80%放电深度(电池不完全放电延长寿命)
double usable_Wh = battery_Wh * 0.8;
std::cout << std::fixed << std::setprecision(1);
std::cout << " 电池容量:" << battery_mAh << " mAh @ " << voltage << "V = "
<< battery_Wh << " Wh\n";
std::cout << " 可用容量(80%放电深度):" << usable_Wh << " Wh\n\n";
std::cout << " 近基站(低功率0.6W,总功耗" << total_low << "W):\n";
std::cout << " 通话时间 = " << usable_Wh / total_low * 60.0
<< " 分钟 ≈ " << usable_Wh / total_low << " 小时\n\n";
std::cout << " 远基站(高功率3.0W,总功耗" << total_high << "W):\n";
std::cout << " 通话时间 = " << usable_Wh / total_high * 60.0
<< " 分钟 ≈ " << usable_Wh / total_high << " 小时\n\n";
std::cout << " CDMA功率控制的意义:\n";
std::cout << " 通过精确控制发射功率(不仅仅是2档),\n";
std::cout << " 大多数时候以低于3W的功率工作,显著延长通话时间。\n";
std::cout << "\n";
std::cout << std::string(60, '=') << "\n";
std::cout << " 计算完成\n";
std::cout << std::string(60, '=') << "\n\n";
return 0;
}
ASCII 总结:嵌入式系统的设计哲学
桌面/服务器设计思维:
+---------------------------------------------+
| CPU → L1缓存 → L2缓存 → L3缓存 → 主存 |
| 统一内存层次,硬件自动管理,对程序员透明 |
+---------------------------------------------+
嵌入式/PS2设计思维:
+---------------------------------------------+
| CPU[自己的SRAM] |
| VPU0[自己的指令/数据SRAM] ← 1周期固定延迟 |
| VPU1[自己的指令/数据SRAM] ← 可预测,可实时|
| 图形合成器[片上DRAM] ← 超高带宽 |
| 程序员用DMA手动搬运数据,自己保证一致性 |
+---------------------------------------------+
核心权衡:
桌面:通用性 + 自动化 = 高峰值性能(平均情况)
嵌入式:专用性 + 手动控制 = 可预测性能(最坏情况)
本文覆盖了教材附录E的全部核心内容,包括DSP原理(MAC、定点数、饱和算术)、SIMD媒体扩展、嵌入式基准测试、多处理器设计,以及PS2、数码相机、手机三个完整案例。两段C++代码可直接编译运行验证所有核心计算。
附录 F:互联网络(Interconnection Networks)详细解析
原文出处:Computer Architecture: A Quantitative Approach(计算机体系结构:量化研究方法)
修订者:Timothy M. Pinkston(南加州大学);Jose Duato(巴伦西亚理工大学)
目录
1. 引言
互联网络是并行计算体系结构的核心。随着系统复杂度和集成度不断提升,许多设计者发现"路由数据包比布线更高效"(Bill Dally,2004)。
什么是互联网络?
互联网络将各种"设备"(device)连接成一个可以相互通信的社区。这里的"设备"概念很广泛:
- 芯片内的功能单元(如ALU、缓存)
- 单颗芯片
- 一台计算机
- 一个计算机集群
典型互联网络结构示意:
[设备A]---[HW接口]---[SW接口]---[端节点A]
|
[互联网络(黑盒)]
|
[设备B]---[HW接口]---[SW接口]---[端节点B]
为什么计算机架构师需要关注互联网络?
- 网络被广泛用于单台计算机内部各层次之间的互联(包括处理器微架构)
- 交换网络正在取代总线,成为计算机间、I/O设备间、板卡间、芯片间乃至芯片内部模块间通信的主要手段
- 不理解互联问题,就无法有效地设计和评估计算机系统
2. 互联网络的四大域
根据连接设备的数量和距离,互联网络分为四大域:
2.1 四大域对比表
| 域名 | 英文缩写 | 连接设备数 | 典型距离 | 典型例子 |
|---|---|---|---|---|
| 片上网络 | OCN(On-Chip Network) | 几十~几百 | 厘米级 | Intel Teraflops(80核)、TILE-Gx100(100核) |
| 系统/存储区域网络 | SAN | 几百~几万 | 几十米~几百米 | InfiniBand(最远300m,120Gbps)、IBM Blue Gene/L |
| 局域网 | LAN | 几百~几千 | 几千米~几十千米 | 以太网(10Gbps,最远40km) |
| 广域网 | WAN | 数百万台 | 全球 | ATM(异步传输模式) |
2.2 四大域的关系图
距离(米)
|
5×10^6 | [ WAN ]
|
5×10^3 | [ LAN ]
|
5×10^0 | [ SAN ]
|
5×10^-3| [OCN]
|_________________________________________________
1 10 100 1000 10000 >100000
连接设备数量
各域之间存在重叠,这些重叠区域是产品竞争的战场。
3. 两设备互联:基础概念
3.1 消息的组成
当两个设备通信时,传输的基本单位叫消息(message)。
消息结构:
+--------+------------------+----------+
| 头部 | 有效载荷 | 尾部 |
| Header | Payload | Trailer |
+--------+------------------+----------+
包含: 包含:
- 目标端口 - 校验和(CRC)
- 序列号
- 消息类型
(00=请求, 01=应答,
10=请求确认, 11=应答确认)
消息有效载荷(Payload):真正需要传输的数据(地址 + 数据内容)
头部/尾部(Header/Trailer):网络控制信息(消息类型、路由信息、错误检测码等)
3.2 数据包(Packet)
当消息超过最大传输单元(MTU)时,消息被拆分为若干数据包(Packet / Datagram):
- 每个数据包在头部包含消息ID(区分属于哪条消息)
- 每个数据包包含序列号(用于目的端重新排序)
- 到达目的地后,数据包被重新组装成消息
3.3 通信协议的步骤
发送端步骤(从上到下):
- 应用程序调用系统调用,将数据拷贝到OS或网络接口缓冲区,将消息拆分为数据包,生成头部和尾部
- 计算校验和(CRC),写入头部或尾部
- 启动计时器,网络接口硬件发送数据包
接收端步骤(从下到上,与发送相反): - 网络接口硬件接收数据包,存入缓冲区
- 计算数据包校验和。若匹配,发送确认(ACK);若不匹配,丢弃数据包(等待发送端超时重传)
- 所有数据包校验通过后,系统重组消息,将数据拷贝到用户地址空间,通知应用程序
发送端响应确认:
- 收到ACK → 从缓冲区释放对应数据包
- 超时未收到ACK → 重发数据包,重启计时器
3.4 流量控制(Flow Control)
流量控制解决"发送方速度超过接收方处理速度"的问题。
两种主要流量控制技术对比:
Xon/Xoff(停走法):
发送端 接收端
|--数据包1--> |
|--数据包2--> | (缓冲区将满,发"停止"信号)
|<-- STOP -------------|
|(暂停发送) | (处理完毕,发"继续"信号)
|<-- GO ---------------|
|--数据包3--> |
信用计数(Credit-based):
初始:发送端credit=N(等于接收端缓冲区容量)
发送端 接收端
|--数据包1--> credit=N-1|
|--数据包2--> credit=N-2| (处理数据包1,返回1个credit)
|<-- credit 1 ---------| credit=N-1
|--数据包3--> credit=N-1|
...
当credit=0时,发送端暂停
两种方法比较:
| 特性 | Xon/Xoff | 信用计数 |
|---|---|---|
| 控制流量大小 | 少(仅在高低水位切换时发信号) | 多(每消费一个包就返回一个credit) |
| 所需缓冲区大小 | 较大(需容纳"停止"信号传播期间到达的包) | 较小(不超过credit数量) |
| 溢出风险 | 有(若停止信号太迟) | 无(credit为0时停止) |
3.5 流量控制缓冲区大小计算示例
题目: 链路原始带宽 8 Gbps,数据包大小 100 字节,使用信用计数流量控制。导体中光速约为真空光速的 2/3。求各距离下所需最少信用数(即缓冲区大小)。
分析: 发送端连续发包,第一个信用必须在发送端耗尽信用之前返回,即:
往返传播延迟 ≤ 数据包传输时间 × 信用数 \text{往返传播延迟} \leq \text{数据包传输时间} \times \text{信用数} 往返传播延迟≤数据包传输时间×信用数
2 × d ( 2 / 3 ) × 3 × 10 5 km/s ≤ 100 B 8 Gbps × 信用数 \frac{2 \times d}{(2/3) \times 3\times10^5 \text{ km/s}} \leq \frac{100 \text{ B}}{8 \text{ Gbps}} \times \text{信用数} (2/3)×3×105 km/s2×d≤8 Gbps100 B×信用数
整理得:
信用数 ≥ 2 d / 链路速度 ( 100 × 8 ) / ( 8 × 10 9 ) \text{信用数} \geq \frac{2d/\text{链路速度}}{(100\times8)/(8\times10^9)} 信用数≥(100×8)/(8×109)2d/链路速度
计算结果:
| 距离 | 最少信用数(即缓冲区大小,以数据包为单位) |
|---|---|
| 1 cm(OCN) | 1 |
| 1 m(SAN) | 1 |
| 100 m(SAN/LAN边界) | 10 |
| 10 km(LAN/WAN) | 1000 |
Xon/Xoff所需缓冲区是信用计数的2倍以上。
3.6 性能指标:延迟与有效带宽
数据包延迟的各组成部分
总延迟 = 发送开销 ⏟ 端节点软硬件 + 飞行时间 ⏟ 传播延迟 + 数据包大小 带宽 ⏟ 传输时间 + 接收开销 ⏟ 端节点软硬件 \text{总延迟} = \underbrace{\text{发送开销}}_{\text{端节点软硬件}} + \underbrace{\text{飞行时间}}_{\text{传播延迟}} + \underbrace{\frac{\text{数据包大小}}{\text{带宽}}}_{\text{传输时间}} + \underbrace{\text{接收开销}}_{\text{端节点软硬件}} 总延迟=端节点软硬件
发送开销+传播延迟
飞行时间+传输时间
带宽数据包大小+端节点软硬件
接收开销
各术语精确定义:
- 带宽(Bandwidth):单位时间内可传输的最大信息量(含头部、载荷、尾部),单位 bits/s
- 飞行时间(Time of flight):数据包第一个比特到达接收端的时间(含链路传播延迟、中继器延迟等)
- WAN:毫秒级;LAN:微秒级;SAN:纳秒级;OCN:皮秒级
- 传输时间(Transmission time) = 数据包大小 / 链路带宽(零负载假设)
- 传输延迟(Transport latency) = 飞行时间 + 传输时间
- 发送开销(Sending overhead):端节点准备数据包注入网络的时间(含软硬件)
- 接收开销(Receiving overhead):端节点处理接收数据包的时间(通常 > 发送开销)
各网络域延迟计算示例
条件: 8 Gbps链路,100字节数据包
| 网络域 | 距离 | 发送开销(x) | 接收开销(4x/3) | 总延迟 |
|---|---|---|---|---|
| OCN | 0.5 cm | 5 ns | 5 ns | 约110 ns |
| SAN | 5 m | 0.305 µs | 0.405 µs | 0.835 µs |
| LAN | 5 km | 3.005 µs | 4.005 µs | 32.11 µs |
| WAN | 5000 km | 30.005 µs | 40.005 µs | 25.07 ms |
结论:WAN的延迟几乎完全由传播延迟主导,OCN和SAN的延迟则由开销和传输时间主导。
有效带宽(Effective Bandwidth)
当多个数据包以流水线方式连续发送时(链路流水线,Link Pipelining):
B W 链路注入 = 数据包大小 max ( 发送开销 , 传输时间 ) BW_{\text{链路注入}} = \frac{\text{数据包大小}}{\max(\text{发送开销},\ \text{传输时间})} BW链路注入=max(发送开销, 传输时间)数据包大小
B W 链路接收 = 数据包大小 max ( 接收开销 , 传输时间 ) BW_{\text{链路接收}} = \frac{\text{数据包大小}}{\max(\text{接收开销},\ \text{传输时间})} BW链路接收=max(接收开销, 传输时间)数据包大小
两设备互联时的有效带宽(考虑双向):
有效带宽 = 2 × 数据包大小 max ( 开销 , 传输时间 ) \text{有效带宽} = \frac{2 \times \text{数据包大小}}{\max(\text{开销},\ \text{传输时间})} 有效带宽=max(开销, 传输时间)2×数据包大小
其中 开销 = max ( 发送开销 , 接收开销 ) \text{开销} = \max(\text{发送开销},\ \text{接收开销}) 开销=max(发送开销, 接收开销)
结论: 当传输时间 > 开销时,有效带宽 = 聚合网络带宽(100%利用率),与飞行时间和距离无关(前提:为信用计数和Xon/Xoff提供足够缓冲区)。
4. 多设备互联:共享媒体与交换媒体
4.1 三个额外网络功能
连接两台以上设备时,需要额外三种机制:
拓扑(Topology) → 解决"有哪些路径可用?"
|
路由(Routing) → 解决"允许走哪些路径?"
|
仲裁(Arbitration)→ 解决"路径什么时候可用?"
|
交换(Switching) → 解决"如何为数据包分配路径?"
4.2 共享媒体网络(Shared-Media Networks)
最简单的多设备互联方式:所有设备共用同一条物理链路(如总线)。
共享总线示意:
[节点0]--+--[节点1]--+--[节点2]--+--[节点3]
| | |
[共享总线(每次只能一个节点发送)]
分布式仲裁机制(以太网CSMA/CD):
- 载波侦听(Carrier Sensing):发送前先"听",确认总线空闲
- 碰撞检测(Collision Detection):发送同时继续"听",检测数据是否被破坏
- 随机退避(Random Back-off):发生碰撞后,随机等待一段时间再重试,指数增长退避时间
令牌传递(Token Passing):另一种公平仲裁方式,令牌持有者才有发送权,令牌在节点间循环传递。
共享媒体网络特点:
- 优点:成本低,路由极简单(每个节点检查是否是目标)
- 支持广播(Broadcasting)和组播(Multicasting)
- 缺点:聚合带宽不随节点数增加,有扩展性瓶颈
4.3 交换媒体网络(Switched-Media Networks)
将被动的点对点链路和主动的交换组件组合为交换结构(Switch Fabric)。
交换媒体网络示意:
[节点0]--\ /--[节点2]
[交换机]
[节点1]--/ \--[节点3]
多对节点可以同时通信,聚合带宽大幅提升
交换媒体网络特点:
- 聚合带宽随节点数增长而扩展
- 支持多对节点同时通信
- 缺点:若资源增长超线性,成本可能过高
4.4 两者性能对比示例
当节点数 N 从 4 增长到 1024 时:
有效带宽
OCN交换
/
SAN交换/
LAN交换 /
WAN交换 /
/ <-- 随N增长
--------------
所有共享媒体(带宽恒定,不随N增长)
交换媒体网络的有效带宽随节点数增加而提升,共享媒体网络的有效带宽恒定(每次只有一个节点能发包)。
5. 网络拓扑
拓扑(Topology)定义了交换机、链路和端节点的物理连接结构,决定了"有哪些路径可用"。
5.1 集中式交换网络(Indirect Networks)
5.1.1 交叉开关(Crossbar)
N个端口的交叉开关可以同时建立 N 对不相冲突的连接。
4×4交叉开关(黑点为交叉点开关):
输入 0 1 2 3 输出
0 ---•---•---•---•---
1 ---•---•---•---•---
2 ---•---•---•---•---
3 ---•---•---•---•---
交叉点数 = N² (成本随N平方增长)
- 非阻塞(Non-blocking):任意一对源-目标都可以同时建立连接
- 成本高: O ( N 2 ) O(N^2) O(N2)
5.1.2 多级互联网络(MIN, Multistage Interconnection Network)
将大交叉开关拆分为多级小交换机,降低成本为 O ( N log N ) O(N \log N) O(NlogN)。
Omega 网络(完美洗牌交换)示例(8端口):
输入端 第1级 第2级 第3级 输出端
0 ---[2x2交换]---[2x2交换]---[2x2交换]--- 0
1 ---| | | |-- 1
2 ---| | | |-- 2
3 ---| | | |-- 3
4 ---| | | |-- 4
5 ---| | | |-- 5
6 ---| | | |-- 6
7 ---| | | |-- 7
使用 k×k 交换机时,共需 logk(N) 级,每级 N/k 个交换机
总交换机数 = (N/k) × logk(N)
Omega 网络存在阻塞问题:不同源到不同目标的路径可能冲突。
5.1.3 解决阻塞的方案
方案一:Clos 网络(可重配置无阻塞)
- 增加 log k N − 1 \log_k N - 1 logkN−1 级镜像交换阶段
- 跳数翻倍,需中央控制重排现有路径
方案二:Beneš 网络(可重配置无阻塞) - 对 Clos 递归分解,直至全部使用 2 × 2 2\times2 2×2 交换机
- 共 2 ( log 2 N ) − 1 2(\log_2 N) - 1 2(log2N)−1 级
胖树网络(Fat Tree):
[根级交换机]
/ \
[中级交换机] [中级交换机]
/ \ / \
[叶级] [叶级] [叶级] [叶级]
交换机 交换机 交换机 交换机
/\ /\ /\ /\
节点 节点 节点 节点 节点 节点 节点 节点
每个树层级保持总链路带宽恒定("胖"的含义)。胖树等价于折叠的 Beneš 网络,是商业系统中最流行的拓扑结构(Myrinet、Mellanox、Quadrics 均基于胖树)。
5.1.4 成本对比(以4096节点为例)
| 方案 | 交换机成本(相对值) | 链路成本(相对值) |
|---|---|---|
| 单交叉开关 | 基准(很高) | 2/13 ≈ 0.15(较低) |
| 2×2 MIN | 1/170(极低) | 约2倍(偏高) |
| 16×16 MIN | 1/85(低) | 约0.5倍 |
交叉开关的交换机成本比 MIN 高出85~170倍,但链路成本略低。
5.2 分布式交换网络(Direct Networks)
将交换机分散嵌入每个端节点,节点直接相互连接(也称直连网络)。
5.2.1 全连接(Fully Connected)
每个节点与所有其他节点直接相连。
- 连接数: N ( N − 1 ) / 2 N(N-1)/2 N(N−1)/2(每节点需 N − 1 N-1 N−1 个端口)
- 无阻塞,但成本比交叉开关还高(约交叉开关的2倍)
- 实际中不使用
5.2.2 环形(Ring)
每个节点只与相邻两个节点相连(折叠环减少最长链路)。
环形拓扑(8节点,折叠后):
[0]--[1]--[2]--[3]
| |
[7]--[6]--[5]--[4]
平均跳数 = N/4
每节点3×3交换机,共N个交换机,N条双向网络链路
- 成本最低(线性成本)
- 延迟较高(大网络下平均跳数多)
5.2.3 二维网格/网格阵列(2D Mesh)和二维环面(2D Torus)
2D Mesh(4×4): 2D Torus(4×4,加环绕链路):
[0]--[1]--[2]--[3] [0]--[1]--[2]--[3]--(回到0)
| | | | | | | |
[4]--[5]--[6]--[7] [4]--[5]--[6]--[7]
| | | | | | | |
[8]--[9]-[10]-[11] [8]--[9]-[10]-[11]
| | | | | | | |
[12]-[13]-[14]-[15] [12]-[13]-[14]-[15]
| | | |
(回到上方各节点)
- Torus 在各维度上加环绕链路,减少平均跳数
- 商用案例:Intel Paragon(2D mesh)、Cray XT3(3D torus)、IBM Blue Gene/L(3D torus)
5.2.4 超立方体(Hypercube / n-cube)
n n n 维超立方体连接 2 n 2^n 2n 个节点,每个节点有 n n n 条链路(每维各一条)。
3D 超立方体(8节点):
4---5
/| /|
6---7 |
| 0-|-1
|/ |/
2---3
每个节点3条链路,节点i与i在某一位上翻转后的节点相连
- 连通性好,跳数少( n = log 2 N n = \log_2 N n=log2N)
- 链路数随维数增多,成本高
5.2.5 k-ary n-cube 统一框架
上述拓扑都是 k-ary n-cube 的特例:
- k k k:每个维度上的节点数
- n n n:维度数
| 拓扑 | k | n |
|---|---|---|
| 环形(N节点) | N | 1 |
| 2D Mesh | N \sqrt{N} N | 2 |
| 超立方体 | 2 | log 2 N \log_2 N log2N |
5.3 各拓扑性能与成本汇总(64节点)
| 拓扑 | 对分带宽(链路数) | 最大跳数(平均) | 每交换机端口数 | 交换机数 | 网络链路数 |
|---|---|---|---|---|---|
| 总线 | 1 | 1(1) | N/A | N/A | 1 |
| 环形 | 2 | 32(16) | 3 | 64 | 64 |
| 2D Mesh | 8 | 14(7) | 5 | 64 | 112 |
| 2D Torus | 16 | 8(4) | 5 | 64 | 128 |
| 超立方体 | 32 | 6(3) | 7 | 64 | 192 |
| 胖树 | 32 | 11(9) | 4 | 192 | 320 |
| 全连接 | 1024 | 1(1) | 64 | 64 | 2016 |
5.4 对分带宽(Bisection Bandwidth)
将网络从中间切成两个相等部分,切割线上所有链路的带宽之和。
B W 对分 = 穿过对分线的链路数 × 每条链路带宽 BW_{\text{对分}} = \text{穿过对分线的链路数} \times \text{每条链路带宽} BW对分=穿过对分线的链路数×每条链路带宽
全对分带宽(Full Bisection Bandwidth): B W 对分 = N / 2 BW_{\text{对分}} = N/2 BW对分=N/2(等于注入/接收链路总带宽的一半)
- 完全连接网络对分带宽最高: N 2 / 4 N^2/4 N2/4 条双向链路
- 总线对分带宽最低:仅1条链路
5.5 拓扑对有效带宽的影响
B W 网络 = B W 对分 γ BW_{\text{网络}} = \frac{BW_{\text{对分}}}{\gamma} BW网络=γBW对分
有效带宽 = min ( N ⋅ B W 链路注入 , B W 对分 γ , σ ⋅ N ⋅ B W 链路接收 ) \text{有效带宽} = \min\left(N \cdot BW_{\text{链路注入}},\ \frac{BW_{\text{对分}}}{\gamma},\ \sigma \cdot N \cdot BW_{\text{链路接收}}\right) 有效带宽=min(N⋅BW链路注入, γBW对分, σ⋅N⋅BW链路接收)
其中:
- γ \gamma γ(0 < γ \gamma γ ≤ 1):穿过对分线的流量比例
- σ \sigma σ(0 < σ \sigma σ ≤ 1):接收端可接受数据包的平均比例
6. 路由、仲裁与交换
6.1 路由(Routing)
路由算法决定数据包从源到目标走哪条路径。
关键问题:死锁(Deadlock)和活锁(Livelock)
活锁:数据包不停跳转但永远到不了目标(非最小路径导致)。
死锁:一组数据包互相等待对方持有的资源,永远无法前进。
死锁示意(2D Mesh中的循环依赖):
d1
|
s3 --+-- ...
|
d4 <-- s4
数据包1(到d1)占用X方向资源,等待Y方向资源
数据包2(到d2)占用Y方向资源,等待X方向资源
→ 循环依赖,形成死锁
死锁解决策略
死锁避免(Deadlock Avoidance):路由算法限制路径,使循环依赖永远无法形成。
维度序路由(Dimension-Order Routing, DOR):
- 数据包必须先在 X 方向走完,再走 Y 方向(在 2D 网格中)
- 禁止从高维度转向低维度
DOR路由示例(2D Mesh,先X后Y):
源(0,0) → 目标(3,2):
向右走3步(X方向)→ 向上走2步(Y方向)
(0,0) --X--> (1,0) --X--> (2,0) --X--> (3,0) --Y--> (3,1) --Y--> (3,2)
规则:先走完X,才能转Y。禁止Y→X的转向,从而避免环路。
DOR 消除了一半可能的维度转向(从低维到高维允许,反之禁止)。
自适应路由(Adaptive Routing):
- 使用"逃逸虚拟通道(Escape Virtual Channel)"保证死锁自由
- 其他虚拟通道可以自适应选择路径(Duato协议)
- 优点:减少拥塞,提高吞吐量
- 缺点:可能导致数据包乱序到达
死锁恢复(Deadlock Recovery): - 不限制路径,允许死锁发生
- 检测死锁后,主动丢弃或重定向某些数据包以打破死锁
6.2 仲裁(Arbitration)
仲裁决定什么时候为数据包分配所请求的资源(输出端口)。
两阶段仲裁
阶段1(请求):每个输入端口向所需输出端口发送请求
阶段2(授权):每个输出端口仲裁器选择一个请求授权
问题:某输出端口仲裁结果可能导致部分输入端口得不到服务,
即使请求的输出端口空闲也可能出现,降低交换机利用率。
三阶段仲裁(改进版)
阶段1(请求):每个输入端口可向多个输出端口发送请求
阶段2(授权):每个输出端口选择一个请求授权
阶段3(确认):输入端口从多个授权中选择一个,通知对应输出端口
三阶段仲裁的匹配成功率更高,但仲裁复杂度和延迟也增加。
6.3 交换(Switching)
交换决定如何在网络中建立数据包的传输路径。
6.3.1 电路交换(Circuit Switching)
通信流程:
1. 建立阶段:预先分配从源到目标的完整路径(带宽保留)
2. 传输阶段:数据在预留路径上流水线传输
3. 拆除阶段:释放路径资源
优点:路由/仲裁/交换只做一次,头部开销小,适合连续大数据流
缺点:带宽预留期间即使空闲也不能被其他数据包使用,浪费带宽
6.3.2 存储转发交换(Store-and-Forward)
每一跳:完整接收 → 存储 → 路由决策 → 转发
[源] ---完整包---> [中间节点1] ---完整包---> [中间节点2] ---> [目标]
(需存完整包才转发)
延迟 = 跳数 × (传输时间 + 路由/仲裁/交换时间) + 飞行时间
优点:链路完全解耦,支持不同带宽链路互联(WANs中重要)
缺点:延迟随跳数乘法增长
6.3.3 直通交换(Cut-Through Switching)
数据包头部一到达就开始转发,无需等待完整数据包。
虚拟直通(Virtual Cut-Through):
[源] --头部--> [中间节点] (头部到达即可路由和交换)
--数据--> [中间节点] --数据--> [目标]
延迟 = 飞行时间 + (数据包大小 + 跳数×头部大小) / 带宽 + 路由/仲裁/交换延迟
延迟随跳数加法增长(而非乘法),性能优于存储转发。
延迟 = 发送开销 + T 传播 ⋅ ( d + 1 ) + ( τ a + T s ) ⋅ d + 包大小 + d ⋅ 头部大小 带宽 + 接收开销 \text{延迟} = \text{发送开销} + T_{\text{传播}} \cdot (d+1) + (\tau_a + T_s) \cdot d + \frac{\text{包大小} + d \cdot \text{头部大小}}{\text{带宽}} + \text{接收开销} 延迟=发送开销+T传播⋅(d+1)+(τa+Ts)⋅d+带宽包大小+d⋅头部大小+接收开销
蠕虫洞交换(Wormhole Switching):流量控制在比包更小的单位(flit)上执行,缓冲区需求极小,但阻塞的包会跨越多个交换机占用资源,可能引发网络提前饱和。
虚拟通道(Virtual Channels):在一条物理链路上复用多条逻辑通道,允许数据包绕过前方阻塞的包,既用于死锁避免,也用于提升带宽利用率。
6.3.4 各交换技术对比
| 技术 | 流量控制粒度 | 缓冲区需求 | 空载延迟 | 吞吐量 | 适用场合 |
|---|---|---|---|---|---|
| 电路交换 | 电路级 | 极小 | 低(大量连续流时) | 低(空闲带宽浪费) | 连续大流量 |
| 存储转发 | 数据包级 | 完整数据包 | 高(乘法跳数延迟) | 高(不同带宽链路互联) | WAN |
| 虚拟直通 | 数据包级 | 完整数据包 | 低(加法跳数延迟) | 较高 | SAN、OCN(主流) |
| 蠕虫洞 | flit级 | 极小(flit大小) | 低(同虚拟直通) | 较低(预饱和风险) | OCN(资源极限时) |
6.4 路由/仲裁/交换对性能的影响
引入效率因子 ρ \rho ρ(0 < ρ \rho ρ ≤ 1):
B W 网络 = ρ ⋅ B W 对分 γ BW_{\text{网络}} = \rho \cdot \frac{BW_{\text{对分}}}{\gamma} BW网络=ρ⋅γBW对分
有效带宽 = min ( N ⋅ B W 链路注入 , ρ ⋅ B W 对分 γ , σ ⋅ N ⋅ B W 链路接收 ) \text{有效带宽} = \min\left(N \cdot BW_{\text{链路注入}},\ \rho \cdot \frac{BW_{\text{对分}}}{\gamma},\ \sigma \cdot N \cdot BW_{\text{链路接收}}\right) 有效带宽=min(N⋅BW链路注入, ρ⋅γBW对分, σ⋅N⋅BW链路接收)
实例(4096节点3D Torus,均匀流量):
| 路由策略 | 虚拟通道数 | 峰值吞吐量 | 效率 ρ \rho ρ |
|---|---|---|---|
| 确定性DOR | 2 VC | 0.37 B/cycle/node | 74% |
| 确定性DOR | 4 VC | 更高 | 更高 |
| 自适应路由 | 2 VC | 更高 | 更高 |
| 自适应路由 | 4 VC | 0.43 B/cycle/node | 86% |
理论最大吞吐量: B W 对分 / γ = 0.25 / 0.5 = 0.5 BW_{\text{对分}} / \gamma = 0.25 / 0.5 = 0.5 BW对分/γ=0.25/0.5=0.5 B/cycle/node
7. 交换机微架构
7.1 基本结构
输入端口 输出端口
+--路由控制单元--+
物理信道 → 解复用 → 输入缓冲区 → 仲裁单元 → 内部交叉开关 → 输出缓冲区 → 复用 → 物理信道
物理信道 → 解复用 → 输入缓冲区 ------^ 内部交叉开关 → 输出缓冲区 → 复用 → 物理信道
7.2 缓冲区位置策略
输出缓冲交换机:
- 消除队头阻塞(HOL Blocking)
- 需要内部加速(write bandwidth = k × link bandwidth)
- 成本较高
输入缓冲交换机: - 无需加速,成本低
- 存在队头阻塞(HOL Blocking)
队头阻塞(Head-of-Line Blocking):
输入端口i的队列: [包→X+] [包→Y+] [包→Y-]
阻塞!
若包→X+被阻塞,后面的包→Y+和→Y-即使目标端口空闲,也无法通过。
这可能使交换机输出端口利用率降至60%以下。
解决HOL阻塞的方法:
- 虚拟通道(Virtual Channels):
输入端口i: VC0: [包→X+] [包→X-] VC1: [包→Y+] [包→Y-] 不同VC的包互不阻塞 - 虚拟输出队列(VOQ, Virtual Output Queuing):
输入端口i: 队列→X+ : [包] [包] 队列→X- : [包] 队列→Y+ : [包] [包] [包] 队列→Y- : [包] 每个输出端口一个队列,彻底消除HOL阻塞 缺点:队列数 = 输入端口数 × 输出端口数,成本随端口数平方增长 - 组合输入输出缓冲(Input-Output Buffered):综合两者优点,商用系统最常见。
7.3 五级流水线交换机微架构
阶段1(IB) → 阶段2(RC) → 阶段3(SA) → 阶段4(ST) → 阶段5(OB)
IB: 链路控制与输入缓冲
- 信号解码、反串行化
- 接收并缓存数据包
RC: 路由计算
- 读取包头,查路由表(或状态机)
- 计算允许的输出端口
SA: 交叉开关仲裁
- 对请求的输出端口进行仲裁
ST: 交叉开关传输
- 配置交叉开关,将包传送到输出端口
OB: 输出缓冲与链路控制
- 缓存包,通过外部链路发送
注意:头部经过全部5个阶段;载荷和尾部跳过RC/SA(路由计算和仲裁),只经过IB→ST→OB。
7.4 路由算法的实现:LBDR(逻辑路由分布路由)
针对OCN中出现的不规则拓扑(制造缺陷、故障等导致),LBDR使用少量配置位实现灵活路由:
- 4个连通位(Cn, Ce, Cw, Cs):指示本交换机与四个方向邻居的连通性
- 8个路由位(Rne, Rnw, Ren, Res, Rwn, Rws, Rse, Rsw):每个输出端口2位,定义允许的转向
// LBDR 路由决策逻辑(C++ 伪代码,展示核心思想)
#include <cstdint>
#include <cassert>
struct LBDRConfig {
// 连通位:该方向是否有连接
bool Cn, Ce, Cw, Cs;
// 路由位:从一个方向到另一个方向的转向是否允许
bool Rne, Rnw, Ren, Res, Rwn, Rws, Rse, Rsw;
};
// 计算数据包应走的输出端口
// curr_x, curr_y: 当前节点坐标
// dst_x, dst_y: 目标节点坐标
// cfg: 当前节点的LBDR配置位
struct PortDecision {
bool N, E, W, S, L; // L = 本地节点(目标就是自己)
};
PortDecision lbdr_route(int curr_x, int curr_y,
int dst_x, int dst_y,
const LBDRConfig& cfg) {
PortDecision port = {false, false, false, false, false};
// 方向信号:需要向哪个方向移动?
bool Ep = (dst_x > curr_x); // 需要向东(x+)
bool Wp = (dst_x < curr_x); // 需要向西(x-)
bool Np = (dst_y > curr_y); // 需要向北(y+)
bool Sp = (dst_y < curr_y); // 需要向南(y-)
// 北端口:需要向北,且不需要东/西;或需要北且需要东(允许NE转向);或需要北且需要西(允许NW转向)
port.N = cfg.Cn && (
(Np && !Ep && !Wp) ||
(Np && Ep && cfg.Rne) ||
(Np && Wp && cfg.Rnw)
);
// 东端口
port.E = cfg.Ce && (
(Ep && !Np && !Sp) ||
(Ep && Np && cfg.Ren) ||
(Ep && Sp && cfg.Res)
);
// 西端口
port.W = cfg.Cw && (
(Wp && !Np && !Sp) ||
(Wp && Np && cfg.Rwn) ||
(Wp && Sp && cfg.Rws)
);
// 南端口
port.S = cfg.Cs && (
(Sp && !Ep && !Wp) ||
(Sp && Ep && cfg.Rse) ||
(Sp && Wp && cfg.Rsw)
);
// 本地(目标就是当前节点)
port.L = (!Np && !Ep && !Wp && !Sp);
return port;
}
int main() {
LBDRConfig cfg;
// 配置一个普通节点(四个方向都连通,允许所有转向)
cfg.Cn = cfg.Ce = cfg.Cw = cfg.Cs = true;
cfg.Rne = cfg.Rnw = cfg.Ren = cfg.Res = true;
cfg.Rwn = cfg.Rws = cfg.Rse = cfg.Rsw = true;
// 从节点(0,0)路由到(3,2)
PortDecision pd = lbdr_route(0, 0, 3, 2, cfg);
assert(pd.E == true); // 首先向东
assert(pd.N == false); // 还没到x目标,不走北
// 从节点(3,0)路由到(3,2)(已到达目标x坐标)
pd = lbdr_route(3, 0, 3, 2, cfg);
assert(pd.N == true); // 向北
assert(pd.E == false); // 不向东
return 0;
}
8. 实际商业问题
8.1 拥塞管理(Congestion Management)
拥塞:过多数据包争用同一链路,需求超过供给。
拥塞本身不降低性能(阻塞链路满负荷运行),但队头阻塞(HOL Blocking) 导致去往非拥塞目标的包被阻塞,浪费带宽,性能下降。
三种基本拥塞控制方案:
| 方案 | 机制 | 缺点 |
|---|---|---|
| 丢包(Packet Discarding) | 缓冲区满时丢弃数据包,靠上层软件重传 | 大量带宽浪费(重传);仅适用于有损网络(Internet) |
| 反压流量控制(Backpressure) | 缓冲区满时,流量控制向发送方传播"停止"信号 | 源端感知拥塞太迟;不主动缓解拥塞;适合无损网络(SAN) |
| 阻塞包(Choke Packets) | 交换机检测到拥塞时,向源端发回阻塞通知,源端降低注入速率 | 若反馈延迟长(WAN),可能不稳定(反应过慢或震荡) |
消除拥塞影响:区域显式拥塞通知(RECN):动态分配少量队列专门存放去往拥塞热点的包,类似于缓存的工作方式,防止HOL阻塞扩散。
8.2 容错性(Fault Tolerance)
系统故障概率随集成密度和设备数增加而上升。
两类故障:
- 瞬态故障:电磁干扰等,可通过重传恢复
- 永久故障:过热、老化等,需要物理路径绕行
三类永久故障处理技术:
- 资源备份(Resource Sparing):备用交换结构切换(如 ServerNet 双交换结构)
- 缺点:可能需要关闭大量健康资源以维持基本拓扑结构
- 容错路由(Fault-Tolerant Routing):利用拓扑中已有的多条路径绕过故障
- Cray T3E(3D Torus)、ASCI White/Purple(MIN)采用此方案
- 难点:保证存在故障时路由算法仍无死锁
- 网络重配置(Network Reconfiguration):重新发现无故障部分的拓扑,重新计算路由表并分发
- 最灵活,支持热插拔(Hot Swapping)
- 难点:动态更新路由表期间保证无死锁(多个路由算法可能同时存在)
- InfiniBand、Myrinet、Quadrics 等均支持
8.3 故障容忍量化示例
数据: 58台工作站,373天内的重启记录
- 有故障的1小时区间:369个(共8974个)
- 每台机器平均故障:654/58 ≈ 11.28小时
故障不容忍网络(一台机器故障整网瘫痪)下无法工作的概率:
P 不容忍 = 369 8974 ≈ 4.1 % P_{\text{不容忍}} = \frac{369}{8974} \approx 4.1\% P不容忍=8974369≈4.1%
传统容错LAN(仅影响该工作站本身)下无法工作的概率:
P 容忍 = 11.28 8974 ≈ 0.13 % P_{\text{容忍}} = \frac{11.28}{8974} \approx 0.13\% P容忍=897411.28≈0.13%
P 不容忍 P 容忍 = 4.1 % 0.13 % ≈ 30 倍 \frac{P_{\text{不容忍}}}{P_{\text{容忍}}} = \frac{4.1\%}{0.13\%} \approx 30 \text{ 倍} P容忍P不容忍=0.13%4.1%≈30 倍
结论:故障不容忍的LAN使用户无法工作的可能性是传统LAN的30倍!
9. 互联网络实例
9.1 OCN 实例:Intel 单芯片云计算(SCC)
- 芯片规格:48个 IA-32 架构核心,24个瓦片(每瓦片2核)
- 拓扑:2D Mesh(6×4)
- 路由:XY 维度序路由,预计算输出端口
- 交换:虚拟直通(Virtual Cut-Through)
- 流量控制:基于信用的链路级流量控制
- 虚拟通道:8个(6个用于性能,2个用于协议级死锁处理)
- 链路:16字节数据 + 2字节边带,运行于2GHz
- 零负载延迟:4个周期(含链路遍历)
- 特色:支持消息传递协议(而非硬件缓存一致性),支持电压/频率分区调节
9.2 SAN 实例:IBM Blue Gene/L 3D Torus 网络
- 规模:65,536个双处理器节点(32×32×64 3D Torus)
- 链路带宽:每方向350 MB/s(bit串行,700 MHz时钟,双边采样 = 1.4 Gbps)
- 注入带宽:612.5 MB/s;接收带宽:1050 MB/s(接收>注入,防止接收成为瓶颈)
- 路由:自适应路由 + 气泡流量控制(Bubble Flow Control)避免死锁
- 交换:虚拟直通,4个虚拟通道
- 最大链路长度:8.6米
- 容错:故障中间板整体绕过,保持3D Torus拓扑结构,从上一个检查点恢复计算
- 集体操作:专用的两个树状网络(barrier/broadcast/reduction),不用3D Torus
9.3 SAN 标准:InfiniBand
- 标准化:2000年10月 InfiniBand Trade Association 发布
- 带宽:2~120 Gbps/链路/方向
- 距离:最远300米
- 交换技术:直通交换(Cut-Through)
- 虚拟通道:16个(15个用于数据)
- 流量控制:基于信用的链路级流量控制
- 路由:可编程转发表(通常用 up*/down* 路由)
- 仲裁:加权轮询公平调度
- 两种通信机制:
- Send/Receive:接收端需预先分配缓冲区
- RDMA(远程直接内存访问):发送端直接写入接收端内存,接收开销大幅降低(从1.423µs降至0.323µs)
9.4 LAN 实例:以太网(Ethernet)
- 标准:IEEE 802.3
- 速度演进:10Mbps(1978)→ 100Mbps → 1Gbps → 10Gbps → 100Gbps
- 原始设计:共享媒体,CSMA/CD仲裁,最多100台设备
- 现代设计:1Gbps以上全部使用点对点链路 + 交换机(存储转发)
- 互联设备:
- Bridge(网桥):连接同类LAN,工作在OSI第2层(数据链路层)
- Router(路由器):连接LAN和WAN,工作在OSI第3层(网络层)
- Hub(集线器):仅扩展LAN范围,不提升带宽,工作在OSI第1层(物理层)
9.5 WAN 实例:ATM
- 带宽:155Mbps → 620Mbps → 2480Mbps(4的倍数扩展)
- 媒介:光纤(单模和多模)
- 特点:固定大小数据包(48字节载荷 + 5字节头部 = 53字节),适合实时语音/视频
- 连接方式:虚连接(Virtual Connection),先建连接再传数据
- 流量控制:信用计数(与IP路由器不实现流量控制相比是优势)
- 现状:在LAN市场竞争失败后退守WAN,最终也被TCP/IP生态侵蚀
10. 互联网络的常见谬误与陷阱
谬误1:互联网络已经足够快,不需要再改进
事实:随着技术进步,互联网络占系统资源、成本和功耗的比例持续上升,尤其是多核处理器的片上网络。
谬误2:对分带宽是网络成本的精确约束
事实:芯片引脚数才是更现实的带宽约束。对分带宽更适合作为性能度量,而非成本约束。
陷阱1:仅用带宽(特别是对分带宽)衡量网络性能
聚合带宽极少是端到端的瓶颈。效率因子 ρ \rho ρ < 100% 和接受因子 σ \sigma σ < 100% 受路由、交换、仲裁和流量特征的强烈影响,只看原始带宽会导致严重误判。
反例: 如图 F.41 所示,在 NFS 流量下,10 Mbps 以太网实际上比 155 Mbps ATM 更快(对于512字节以下的消息),因为ATM的软件开销远高于以太网驱动。
陷阱2:忽略接收链路带宽导致端节点成为瓶颈
非一对一的流量模式下,多个包会同时到达同一目标,造成接收侧拥塞。需分析流量模式并提供额外接收带宽(IBM Blue Gene/L接收带宽是注入带宽的1.7倍)。
谬误3:零拷贝协议不需要任何数据拷贝
零拷贝省去了内核缓冲区拷贝,但通常仍需要从主存拷贝到网卡内存(NIC)。通过I/O子系统访问NIC内存往往比访问主存慢,所以通常在主存中组装消息,再由DMA设备传输到NIC。
陷阱3:忽略软件开销评估性能
硬件开销只是总开销的一部分,软件开销(系统调用、协议处理)往往是主要开销。
实例:
- CM-5:软件开销20µs,硬件开销仅0.5µs
- Intel Paragon初版:硬件0.2µs,软件250µs(后优化至几µs)
- IBM Blue Gene/L:MPI总开销≈3µs,其中硬件≤1µs
这就是 Amdahl 定律在网络中的体现:若软件不优化,更快的网络硬件毫无意义。
谬误4:MIN比直连网络更具成本效益
一个节点可以包含多个计算设备(多核处理器,单片交换机),或每个交换机连接多台设备(Bristling),使直连网络可以同样甚至更具成本效益。
谬误5:低维直连网络性能优于高维(如超立方体)
此结论基于以链路受限(wire-limited)为主要约束,但大多数实现是引脚受限(pin-limited)。高基数(high-radix)交换机实际上更具成本效益。
谬误6:蠕虫洞交换比其他技术性能更好
蠕虫洞和虚拟直通在空载时延迟相同。蠕虫洞在80年代的性能优势主要来源于带宽提升,而非交换技术本身。现代芯片可实现大缓冲区,虚拟直通在吞吐量上通常优于蠕虫洞。
谬误7:几个虚拟通道总是能提升吞吐量
在虚拟直通交换机中,每个虚拟通道本身的大缓冲区可能引入HOL阻塞,在高负载时反而降低性能。虚拟通道的主要价值是流量隔离,而非简单地增加数量。
谬误8:自适应路由导致乱序开销过大
通过合理协议设计可以基本消除重排序开销:
- 短消息用确定性路由(避免乱序)
- 长消息用自适应路由(每个包含序列号/偏移量,目标端直接存入正确位置)
- 完成条件:计数已到达包数而非等最后一个包
谬误9:自适应路由天然提升容错性
自适应路由本身不足以容错,还需要故障检测和通知机制。实际上,同一故障在自适应路由下影响的源-目标对比确定性路由更多。
11. 总结
核心设计原则
互联网络设计是一个端到端的问题,涵盖:
端节点接口(注入/接收链路及开销)
↕
网络交换结构(拓扑、交换机、链路)
↕
端节点接口(注入/接收链路及开销)
关键设计决策树
性能公式速查
| 公式 | 含义 |
|---|---|
| 总延迟 = 发送开销 + T 飞行 + 包大小 带宽 + 接收开销 \text{总延迟} = \text{发送开销} + T_{\text{飞行}} + \frac{\text{包大小}}{\text{带宽}} + \text{接收开销} 总延迟=发送开销+T飞行+带宽包大小+接收开销 | 单包零负载延迟 |
| B W 注入 = 包大小 max ( 发送开销 , 传输时间 ) BW_{\text{注入}} = \frac{\text{包大小}}{\max(\text{发送开销},\ \text{传输时间})} BW注入=max(发送开销, 传输时间)包大小 | 每条注入链路带宽 |
| B W 网络 = ρ ⋅ B W 对分 γ BW_{\text{网络}} = \rho \cdot \frac{BW_{\text{对分}}}{\gamma} BW网络=ρ⋅γBW对分 | 网络内部有效带宽 |
| 有效带宽 = min ( N ⋅ B W 注入 , B W 网络 , σ ⋅ N ⋅ B W 接收 ) \text{有效带宽} = \min(N \cdot BW_{\text{注入}},\ BW_{\text{网络}},\ \sigma \cdot N \cdot BW_{\text{接收}}) 有效带宽=min(N⋅BW注入, BW网络, σ⋅N⋅BW接收) | 端到端有效带宽 |
历史趋势
- 1970-80s:共享媒体(总线/以太网)为主,计算速度慢,网络够用
- 1990s:交换媒体兴起,MPI、InfiniBand,超级计算机大量使用3D Torus和胖树
- 2000s:多核处理器推动片上网络(OCN)快速发展
- 2010s至今:高基数(high-radix)交换机成为主流,单芯片几十~几百端口
附录 G:向量处理器深度解析
原文出处:Computer Architecture: A Quantitative Approach(计算机体系结构:量化研究方法)
修订者:Krste Asanovic(麻省理工学院)
目录
1. 引言
向量处理器是一种能对**数组(向量)**中每个元素同时或流水线式执行相同操作的处理器。与普通标量处理器每次只处理一个数据不同,向量处理器一条指令可以处理几十甚至几百个数据元素。
Seymour Cray(Cray-1 的设计者)在 1976 年的演讲中说道:
“做先驱总会犯错。我从不想做先驱,最好是第二个——可以看清前人的错误。”
这句话道出了工程设计的智慧:站在巨人肩膀上,吸取教训,做出更好的设计。
典型向量处理器参数一览
下表列出了历史上主要向量处理器的关键参数(各处理器单处理器的参数):
| 处理器(年份) | 时钟(MHz) | 向量寄存器数 | 每寄存器元素数 | 功能单元数 | 加载/存储单元 | 通道数 |
|---|---|---|---|---|---|---|
| Cray-1 (1976) | 80 | 8 | 64 | 6 | 1 | 1 |
| Cray X-MP (1983) | 118 | 8 | 64 | 8 | 2载+1存 | 1 |
| NEC SX/2 (1985) | 167 | 8+32 | 256 | 4 | 1 | 4 |
| NEC SX/5 (1998) | 312 | 8+64 | 512 | 4 | 1 | 16 |
| Cray X1 (2002) | 800 | 32 | 64/256(MSP) | 3 | 1载+1存 | 2/8(MSP) |
| 关键概念解释: |
- 向量寄存器(Vector Register):存放一组数值(如64个浮点数)的寄存器,类比普通寄存器存放单个值
- 通道数(Lanes):每个功能单元内并行流水线的数量,通道越多,每周期处理元素越多
- 功能单元(Functional Units):专门执行加法、乘法等运算的硬件模块
2. 向量性能深度分析
2.1 基本性能模型回顾
Convoy(护航组):可以同时发射的一组向量指令(无结构冲突、无数据相关冲突)。
Chime(节拍):执行一个 convoy 所需的大约时间单位(近似为 1 个时钟周期×向量长度)。
Chime 近似公式(忽略启动开销):
T ≈ convoy 数量(chimes) × n T \approx \text{convoy 数量(chimes)} \times n T≈convoy 数量(chimes)×n
其中 n n n 为向量长度。
2.2 启动时间(Start-up Time)
Chime 近似忽略了一个重要开销:向量启动时间(Vector Start-up Time)。
启动时间来源于功能单元的流水线深度(Pipeline Depth)。功能单元越深,启动延迟越大。
流水线深度 = 功能单元总延迟时间 时钟周期时间 \text{流水线深度} = \frac{\text{功能单元总延迟时间}}{\text{时钟周期时间}} 流水线深度=时钟周期时间功能单元总延迟时间
例如,如果一个操作需要 10 个时钟周期,则需要 10 级流水线才能实现每周期出一个结果。
VMIPS 各功能单元的启动开销:
| 功能单元 | 启动开销(周期) |
|---|---|
| 加载/存储单元 | 12 |
| 乘法单元 | 7 |
| 加法单元 | 6 |
| 除法单元 | 20 |
2.3 包含启动时间的精确执行时间
考虑如下向量指令序列(假设向量长度为 n n n):
convoy 1: LV (加载向量)
convoy 2: MULVS.D, LV (乘法 + 加载,chaining)
convoy 3: ADDV.D (加法)
convoy 4: SV (存储向量)
各 convoy 的时序分析(向量长度为 n n n):
convoy 启动时刻 第一个结果时刻 最后一个结果时刻
1 0 12 11 + n
2 12 + n 12+n+12 23 + 2n
3 24 + 2n 24+2n+6 29 + 3n
4 30 + 3n 30+3n+12 41 + 4n
当 n = 64 n = 64 n=64 时:
每个结果的时间 = 41 + 4 × 64 64 = 41 + 256 64 = 297 64 ≈ 4.64 周期/结果 \text{每个结果的时间} = \frac{41 + 4 \times 64}{64} = \frac{41 + 256}{64} = \frac{297}{64} \approx 4.64 \text{ 周期/结果} 每个结果的时间=6441+4×64=6441+256=64297≈4.64 周期/结果
而 Chime 近似只给出 4 周期/结果,实际执行时间比近似值高 16%。
2.4 完整的向量执行时间公式
对于长度为 n n n 的向量,经过分段执行(Strip Mining),完整执行时间为:
T n = ⌈ n M V L ⌉ × ( T loop + T start ) + n × T chime T_n = \left\lceil \frac{n}{MVL} \right\rceil \times (T_{\text{loop}} + T_{\text{start}}) + n \times T_{\text{chime}} Tn=⌈MVLn⌉×(Tloop+Tstart)+n×Tchime
各参数含义:
| 符号 | 含义 | 典型值(VMIPS) |
|---|---|---|
| M V L MVL MVL | 最大向量长度(Maximum Vector Length) | 64 |
| T loop T_{\text{loop}} Tloop | 每次分段循环的标量开销(设置地址、计数等) | 15 周期 |
| T start T_{\text{start}} Tstart | 每次分段循环中所有 convoy 的启动开销之和 | 依序列而定 |
| T chime T_{\text{chime}} Tchime | convoy 数(近似执行时间) | 依指令序列 |
执行时间随向量长度变化的规律:
每元素时间
|
| \ <- 短向量:启动开销主导
| \
| \____________________
| <- 长向量:趋近于 Tchime(稳定)
|________________________________
向量长度 n
n 越长,启动开销被摊薄,效率越高
每次跨越 64 的倍数时出现跳跃(新一次分段循环的开销)
2.5 执行时间计算示例:A = B × s
题目: 向量 A 和 B 长度为 200,s 为标量,计算 A = B × s 在 VMIPS(500 MHz)上的执行时间。
分段执行分析:
- 200 mod 64 = 8,所以第一次迭代处理 8 个元素,后续迭代处理 64 个
- 总迭代次数: ⌈ 200 / 64 ⌉ = 4 \lceil 200/64 \rceil = 4 ⌈200/64⌉=4 次
三条向量指令(依次形成3个 convoy):
convoy 1: LV V1, Rb (加载 B)
convoy 2: MULVS.D V2,V1,Fs (标量乘法)
convoy 3: SV Ra, V2 (存储 A)
因此 T chime = 3 T_{\text{chime}} = 3 Tchime=3, T start = 12 + 7 + 12 = 31 T_{\text{start}} = 12 + 7 + 12 = 31 Tstart=12+7+12=31 周期。
代入公式:
T 200 = 4 × ( 15 + 31 ) + 200 × 3 = 4 × 46 + 600 = 184 + 600 = 784 周期 T_{200} = 4 \times (15 + 31) + 200 \times 3 = 4 \times 46 + 600 = 184 + 600 = 784 \text{ 周期} T200=4×(15+31)+200×3=4×46+600=184+600=784 周期
每个元素的执行时间:
T 200 200 = 784 200 = 3.92 周期/元素 \frac{T_{200}}{200} = \frac{784}{200} = 3.92 \text{ 周期/元素} 200T200=200784=3.92 周期/元素
Chime 近似( T chime = 3 T_{\text{chime}} = 3 Tchime=3)仅为 3 周期/元素,实际比近似值高约 30%。
2.6 流水线指令启动与多通道(Multiple Lanes)
多通道的作用: 增加通道数可以提高峰值性能(每周期处理更多元素),但不能减少启动延迟。
单通道流水线(5级)示意:
时钟 1 2 3 4 5 6 7 8 9
指令1 读 执 执 执 写
[V1元素0]--流水线-->[结果0]
[V1元素1]--流水线-->[结果1]
[V1元素2]--流水线-->[结果2]
死时间(Dead Time)问题:
某些向量机(如 Cray C90)在两条发往同一功能单元的向量指令之间,需要插入若干周期的"死时间",即使它们之间没有数据相关。
死时间对性能的影响计算(Cray C90 示例):
- Cray C90:2个通道,最大向量长度 128,死时间 4 周期
- 128元素向量分布到2个通道,每条功能单元占用: 128 / 2 = 64 128 / 2 = 64 128/2=64 周期
- 加上死时间 4 周期,峰值性能利用率:
64 64 + 4 = 64 68 ≈ 94.1 % \frac{64}{64 + 4} = \frac{64}{68} \approx 94.1\% 64+464=6864≈94.1%
若通道数增加到 16:
128 / 16 128 / 16 + 4 = 8 8 + 4 = 8 12 ≈ 66.6 % \frac{128 / 16}{128/16 + 4} = \frac{8}{8 + 4} = \frac{8}{12} \approx 66.6\% 128/16+4128/16=8+48=128≈66.6%
结论:通道数增多时,死时间占比更大,性能损失更严重。需要改进控制逻辑以消除死时间。
3. 向量存储系统深度分析
3.1 多存储库(Memory Banks)
为了支持每时钟周期取/存一个元素的速率,内存系统需要多个独立存储库分摊访问压力。
所需存储库数量:
最少存储库数 ≥ 存储库访问延迟(周期) \text{最少存储库数} \geq \text{存储库访问延迟(周期)} 最少存储库数≥存储库访问延迟(周期)
实践中取最接近的2的幂次。
3.2 存储库访问时序示例
题目: 从字节地址 136 开始,取 64 个元素的向量。存储器访问延迟 6 个周期,8个存储库。
分析: 元素依次分配到各存储库,每个库一次访问忙 6 个周期:
周期 库0 库1 库2 库3 库4 库5 库6 库7
0 [136]
1 忙 [144]
2 忙 忙 [152]
3 忙 忙 忙 [160]
4 忙 忙 忙 忙 [168]
5 忙 忙 忙 忙 忙 [176]
6 忙 忙 忙 忙 忙 忙 [184]
7 忙 忙 忙 忙 忙 忙 忙 [192]
8 [200] 忙 忙 忙 忙 忙 忙 忙
9 忙 [208] 忙 忙 忙 忙 忙 忙
...
15 [256] 忙 忙 忙 忙 忙 忙 忙
方括号内数字为字节地址,"忙"表示该库正在处理上一次访问。8个库刚好够让每周期都有一个新地址发出,实现无停顿的连续访问(单位步长情况下)。
3.3 存储库冲突(Bank Conflicts)
当访问步长(Stride)与存储库数量存在公因数时,多个访问会落入同一个库,产生存储库冲突:
步长与库数示例(16个库):
步长 = 1: 库0, 1, 2, 3, 4, ..., 15, 0, 1, ... 无冲突
步长 = 8: 库0, 8, 0, 8, 0, 8, ... 每次访问冲突!
步长 = 16: 库0, 0, 0, 0, ... 每次都冲突!
避免冲突的规则: 步长与存储库数量互质(最大公约数为1)时不发生冲突。
增加库数对冲突频率的改善:
| 存储库数 | 步长=8 时的冲突频率 |
|---|---|
| 16 | 每次访问冲突 |
| 64 | 每 8 次访问冲突一次 |
现代向量超级计算机(2011年)将每个 CPU 的访问分散到数百个存储库,以降低冲突概率。
3.4 访问延迟 vs. 库忙时间
| 指标 | 含义 | 影响 |
|---|---|---|
| 访问延迟(Access Latency) | 地址送达到数据返回的时间 | 决定向量加载的启动开销 |
| 库忙时间(Bank Busy Time) | 库处理一次请求后的占用时间 | 决定存储系统的有效带宽 |
- 非流水线 SRAM:两者近似相等
- 流水线 SRAM:访问延迟 > 忙时间(每次访问只占一个流水线阶段)
- DRAM:访问延迟 < 忙时间(DRAM 需要额外时间完成破坏性读取后的数据恢复)
4. 提升向量性能的技术
4.1 链接(Chaining)深度解析
链接(Chaining):允许一条向量指令的结果在全部计算完成之前,就开始被下一条指令消费。类似于流水线中的数据前递(Forwarding)。
早期链接 vs. 灵活链接:
- 早期链接:类似前递,对源指令和目标指令的时序有严格限制
- 灵活链接(Flexible Chaining):只要不产生结构冲突,向量指令可以链接到任意其他活跃的向量指令
链接的实现方式: - 为向量寄存器文件增加更多读写端口,或
- 将向量寄存器存储组织成交错存储库(类似内存系统)
链接的效果:
即使两条指令存在数据相关(如 ADDV 的结果作为 MULV 的输入),通过链接也可以将它们放入同一个 convoy,实现并行执行不同元素:
无链接(分开执行):
ADDV.D V1,V2,V3 [元素0][元素1]...[元素63]
MULV.D V4,V1,V5 [元素0][元素1]...[元素63]
↑等待加法完全结束↑
总时间 ≈ 64+6 + 64+7 = 141 周期
有链接(交叠执行):
ADDV.D V1,V2,V3 [元素0][元素1]...[元素63]
↓6周期后
MULV.D V4,V1,V5 [元素0][元素1]...[元素63]
↑
总时间 ≈ 64 + 6 + 7 = 77 周期
性能对比(向量长度 64,FLOPS 计算):
- 无链接:141 周期, 128 / 141 ≈ 0.9 128 / 141 \approx 0.9 128/141≈0.9 FLOPS/周期
- 有链接:77 周期, 128 / 77 ≈ 1.7 128 / 77 \approx 1.7 128/77≈1.7 FLOPS/周期
链接使吞吐量提升约 89%!现代向量处理器全部支持灵活链接。
4.2 稀疏矩阵处理(Sparse Matrix)
问题背景: 稀疏矩阵中大多数元素为 0,只有少量非零元素。直接用密集向量操作会浪费大量计算。
典型稀疏向量求和代码(FORTRAN):
do 100 i = 1, n
A(K(i)) = A(K(i)) + C(M(i))
100 continue
这里 K 和 M 是索引向量,指向 A 和 C 中的非零元素位置。
向量化的挑战: 编译器无法自动确认 K 中的元素是否有重复(若有重复则存在数据相关,不能并行执行)。需要程序员指令或运行时检测。
两种向量化方案:
方案一:向量掩码(Vector Mask)
使用掩码寄存器标记条件为真的元素,跳过为假的元素:
向量 A: [3, 0, 7, 0, 2, 0, 5, 0]
掩码 VM: [1, 0, 1, 0, 1, 0, 1, 0] ← A(i) != 0
带掩码的运算只对 VM=1 的位置执行,VM=0 的位置跳过
方案二:压缩-散射(Scatter-Gather)
Gather(聚集):只取出非零元素,打包成密集向量
原始: [3, 0, 7, 0, 2]
索引: [0, 2, 4]
结果: [3, 7, 2] ← 只有3个元素
Scatter(散射):将密集向量的结果写回原始位置
密集结果: [结果0, 结果2, 结果4]
索引: [0, 2, 4]
写回原始向量对应位置
两种方案性能对比(忽略链接开销):
- 方案一(掩码)执行时间: 5 n 5n 5n(n为总元素数,含零元素)
- 方案二(scatter-gather)执行时间: 4 n + 4 f n 4n + 4fn 4n+4fn(f为非零元素比例)
当方案二更快时:
5 n > 4 n + 4 f n ⟹ 1 > 4 f ⟹ f < 1 4 5n > 4n + 4fn \implies 1 > 4f \implies f < \frac{1}{4} 5n>4n+4fn⟹1>4f⟹f<41
即非零元素比例低于 25% 时,scatter-gather 更快。
5. 编译器向量化的有效性
向量化的成功取决于两个因素:
- 程序结构:循环是否存在真实数据相关?
- 编译器能力:能否分析并自动向量化?
下表展示了不同编译器对 100 个 FORTRAN 测试内核(均可手工向量化)的自动向量化结果:
| 处理器 | 编译器 | 完全向量化 | 部分向量化 | 未向量化 |
|---|---|---|---|---|
| CDC CYBER 205 | VAST-2 V2.21 | 62 | 5 | 33 |
| Convex C-series | FC 5.0 | 69 | 5 | 26 |
| Cray X-MP | CFT77 V3.0 | 69 | 3 | 28 |
| Cray X-MP | CFT V1.15 | 50 | 1 | 49 |
| Cray-2 | CFT2 V3.1a | 27 | 1 | 72 |
| NEC SX/2 | FORTRAN77/SX | 66 | 5 | 29 |
关键发现:同一台 Cray X-MP,新版编译器(CFT77 V3.0)向量化率比旧版(CFT V1.15)高 38%!编译器质量对向量性能影响巨大。
6. 综合:向量处理器性能指标
6.1 性能度量体系
仅用执行时间 T n T_n Tn 或 MFLOPS 来衡量向量处理器性能是不完整的,还需要以下向量长度相关指标:
三大性能指标:
R ∞ = lim n → ∞ 每次迭代 FLOPS 数 × 时钟频率 T n / n R_\infty = \lim_{n \to \infty} \frac{\text{每次迭代 FLOPS 数} \times \text{时钟频率}}{T_n / n} R∞=n→∞limTn/n每次迭代 FLOPS 数×时钟频率
R ∞ R_\infty R∞(无穷向量 MFLOPS 峰值):向量无限长时能达到的 MFLOPS 率,反映峰值性能但过于乐观。
N 1 / 2 N_{1/2} N1/2(半峰值向量长度):达到 R ∞ / 2 R_\infty / 2 R∞/2 所需的最短向量长度,衡量启动开销的影响。 N 1 / 2 N_{1/2} N1/2 越小,启动开销越轻,短向量性能越好。
N v N_v Nv(向量模式超越标量模式的最短向量长度):同时反映启动开销和向量模式相对标量模式的速度优势。
6.2 DAXPY 循环完整性能分析
DAXPY 是著名的线性代数基准: Y = a × X + Y Y = a \times X + Y Y=a×X+Y
VMIPS 的 DAXPY 内循环 convoy 划分:
convoy 1: LV V1, Rx (加载 X)
MULVS.D V2, V1, F0 (X * a,链接到 LV)
convoy 2: LV V3, Ry (加载 Y)
ADDV.D V4, V2, V3 (X*a + Y,链接到 LV)
convoy 3: SV Ry, V4 (存储结果)
因此 T chime = 3 T_{\text{chime}} = 3 Tchime=3(3个 convoy,1个内存流水线)。
启动开销计算:
T start = 12 ( LV ) + 7 ( MUL ) + 12 ( LV ) + 6 ( ADD ) + 12 ( SV ) = 49 周期 T_{\text{start}} = 12 (\text{LV}) + 7 (\text{MUL}) + 12 (\text{LV}) + 6 (\text{ADD}) + 12 (\text{SV}) = 49 \text{ 周期} Tstart=12(LV)+7(MUL)+12(LV)+6(ADD)+12(SV)=49 周期
完整执行时间( n n n 不是64的整数倍时):
T n = ⌈ n 64 ⌉ × ( 15 + 49 ) + 3 n = ⌈ n 64 ⌉ × 64 + 3 n ≈ n + 64 + 3 n = 4 n + 64 T_n = \left\lceil \frac{n}{64} \right\rceil \times (15 + 49) + 3n = \left\lceil \frac{n}{64} \right\rceil \times 64 + 3n \approx n + 64 + 3n = 4n + 64 Tn=⌈64n⌉×(15+49)+3n=⌈64n⌉×64+3n≈n+64+3n=4n+64
计算 R ∞ R_\infty R∞(500 MHz 时钟):
R ∞ = lim n → ∞ 2 × 500 MHz T n / n = 2 × 500 MHz 4 = 250 MFLOPS R_\infty = \lim_{n \to \infty} \frac{2 \times 500 \text{ MHz}}{T_n / n} = \frac{2 \times 500 \text{ MHz}}{4} = 250 \text{ MFLOPS} R∞=n→∞limTn/n2×500 MHz=42×500 MHz=250 MFLOPS
仅凭 Chime 计算的理论峰值(3 chimes)为: 2 × 500 3 = 333 \frac{2 \times 500}{3} = 333 32×500=333 MFLOPS,
实际峰值 低 25%,差距来自启动开销。
6.3 Linpack 基准上的持续性能
Linpack 对 100×100 矩阵做高斯消去,向量长度从 1 到 99。
加权平均向量长度:
n ˉ = ∑ i = 1 99 i 2 ∑ i = 1 99 i ≈ 66.3 \bar{n} = \frac{\sum_{i=1}^{99} i^2}{\sum_{i=1}^{99} i} \approx 66.3 nˉ=∑i=199i∑i=199i2≈66.3
对 n = 66 n = 66 n=66 的执行时间:
T 66 = 2 × ( 15 + 49 ) + 66 × 3 = 128 + 198 = 326 周期 T_{66} = 2 \times (15 + 49) + 66 \times 3 = 128 + 198 = 326 \text{ 周期} T66=2×(15+49)+66×3=128+198=326 周期
持续 MFLOPS:
R 66 = 2 × 66 × 500 MHz 326 ≈ 202 MFLOPS R_{66} = \frac{2 \times 66 \times 500 \text{ MHz}}{326} \approx 202 \text{ MFLOPS} R66=3262×66×500 MHz≈202 MFLOPS
峰值 R ∞ = 250 R_\infty = 250 R∞=250 MFLOPS,持续性能比峰值低约 19%(不含标量代码的影响)。
6.4 N 1 / 2 N_{1/2} N1/2 和 N v N_v Nv 计算
计算 N 1 / 2 N_{1/2} N1/2(目标:达到 125 MFLOPS):
设 N 1 / 2 < 64 N_{1/2} < 64 N1/2<64,此时 T n = 64 + 3 n T_n = 64 + 3n Tn=64+3n:
125 = 2 N 1 / 2 × 500 T N 1 / 2 ⟹ T N 1 / 2 = 8 N 1 / 2 125 = \frac{2 N_{1/2} \times 500}{T_{N_{1/2}}} \implies T_{N_{1/2}} = 8 N_{1/2} 125=TN1/22N1/2×500⟹TN1/2=8N1/2
64 + 3 N 1 / 2 = 8 N 1 / 2 ⟹ 5 N 1 / 2 = 64 ⟹ N 1 / 2 ≈ 13 64 + 3 N_{1/2} = 8 N_{1/2} \implies 5 N_{1/2} = 64 \implies N_{1/2} \approx 13 64+3N1/2=8N1/2⟹5N1/2=64⟹N1/2≈13
即:向量长度只需 13 个元素,就能达到峰值性能的一半(启动开销较小)。
计算 N v N_v Nv(向量模式开始超越标量模式):
标量执行一次循环体(流水线调度后):约 59 周期。
向量模式( n < 64 n < 64 n<64): T n = 64 + 3 n T_n = 64 + 3n Tn=64+3n 周期。
令两者相等:
64 + 3 N v = 59 N v ⟹ 64 = 56 N v ⟹ N v ≈ 2 64 + 3 N_v = 59 N_v \implies 64 = 56 N_v \implies N_v \approx 2 64+3Nv=59Nv⟹64=56Nv⟹Nv≈2
即:**只要向量有 2 个元素,向量模式就比标量模式快!**这一结果令人惊讶,说明向量模式的启动开销相对于标量循环开销并不大。
6.5 增强 VMIPS 的性能提升
增加内存流水线
增加到 3 个内存流水线后: 所有指令可以放入 1 个 convoy, T chime = 1 T_{\text{chime}} = 1 Tchime=1。
T 66 = 2 × ( 15 + 49 ) + 66 × 1 = 128 + 66 = 194 周期 T_{66} = 2 \times (15 + 49) + 66 \times 1 = 128 + 66 = 194 \text{ 周期} T66=2×(15+49)+66×1=128+66=194 周期
性能提升: 326 / 194 ≈ 1.68 326 / 194 \approx 1.68 326/194≈1.68 倍(而 chimes 提升了 3 倍,Amdahl 定律的体现)。
允许 convoy 重叠(Tailgating 技术)
将各 convoy 和标量循环开销完全重叠,启动开销只需承受一次:
T n ≈ n + T start + T loop = n + 64 T_n \approx n + T_{\text{start}} + T_{\text{loop}} = n + 64 Tn≈n+Tstart+Tloop=n+64
R ∞ = 2 × 500 1 = 1000 MFLOPS R_\infty = \frac{2 \times 500}{1} = 1000 \text{ MFLOPS} R∞=12×500=1000 MFLOPS
T 66 = 66 + 64 = 130 周期 T_{66} = 66 + 64 = 130 \text{ 周期} T66=66+64=130 周期
与原始 T 66 = 326 T_{66} = 326 T66=326 相比,提升 2.5 倍;峰值性能提升 4 倍(250 → 1000 MFLOPS)。
性能提升对比汇总:
配置 T66 R_inf
原始 VMIPS(1内存流水线) 326 250 MFLOPS
+2内存流水线 194 ---
+2内存流水线+convoy重叠 130 1000 MFLOPS
7. 现代向量超级计算机:Cray X1
7.1 Cray X1 体系结构
Cray X1(2002年)是向量超级计算机的代表作。其独特之处在于引入了**多流处理器(MSP)**概念。
层次结构:
关键参数:
| 组件 | 参数 |
|---|---|
| MSP 组成 | 4个 SSP + 共享 Ecache |
| SSP 标量单元 | 400 MHz,双发射乱序超标量 |
| SSP 向量单元 | 800 MHz(是标量的2倍),2通道 |
| 向量寄存器 | 32个,每个64个64位元素 |
| 每 MSP 峰值 | 12.8 GFLOPS |
| Ecache | 2MB,两路组相联,32字节行,50 GB/s |
向量功能单元运行在 800 MHz,是标量单元(400 MHz)的两倍。这是因为向量功能单元的流水线更规整,比超标量指令发射机制更容易实现高频率。
7.2 X1 节点架构
X1 节点(4个 MSP + 内存):
[MSP0][MSP1][MSP2][MSP3]
| | | |
[16个内存控制器芯片]
| 每个控制器8个 Rambus DRAM 通道
[DRAM] 每通道 1.6 GB/s
共128通道,总带宽 > 200 GB/s
X1 系统规模:
- 最多 1024 个节点(4096 MSP,16384 SSP)
- 通过高带宽全局网络互连
- 所有内存可被任何处理器通过 load/store 直接访问(共享内存模型)
未完成缓存一致性的处理方式: - 每个 Ecache 只缓存本节点 DRAM 的数据
- 内存控制器用目录方式维护4个 Ecache 的一致性
- 远端访问直接从内存获取最新值,本地 Ecache 被远端写操作失效
7.3 向量处理器 vs. 超标量处理器:关键差异
向量处理器在以下方面优于超标量微处理器:
内存带宽和内存延迟容忍:
| 特性 | 超标量微处理器 | Cray X1 MSP |
|---|---|---|
| 最大未完成内存请求数 | 8~16 | 2048(每 SSP 512) |
| 缓存行大小 | 64~128 字节(大行减少未命中次数) | 32 字节(短行减少带宽浪费) |
| 非单位步长带宽 | 差(大缓存行浪费带宽) | 好(独立追踪每个请求) |
向量机能容忍大量内存延迟,超标量靠大缓存行弥补,但在非单位步长访问时浪费严重。
7.4 MSP 的三种使用模式
模式一:8通道向量模式(最常用)
4个 SSP 协同模拟一个8通道向量处理器:
1000个元素的向量循环:
SSP0 处理元素 0~249
SSP1 处理元素 250~499
SSP2 处理元素 500~749
SSP3 处理元素 750~999
→ 同步点 → 开始下一循环
模式二:外循环并行模式
当内循环向量太短时,将外循环的不同迭代分配给不同 SSP:
/* 缩放矩阵上三角 */
for (row = 0; row < 100; row++)
for (col = row; col < 100; col++)
A[row][col] *= K;
// 分配方式:
// SSP0: row = 0, 4, 8, ...
// SSP1: row = 1, 5, 9, ...
// SSP2: row = 2, 6, 10, ...
// SSP3: row = 3, 7, 11, ...
// 每个SSP看到的内循环更长,效率更高
模式三:独立线程模式
各 SSP 各自运行独立线程,用于向量并行性有限但线程级并行性丰富的代码。
7.5 Cray X1E(2004年升级版)
- 两个 SSP 集成在单芯片上(首款多核向量微处理器)
- 每节点8个 MSP,组织为两个逻辑节点(保持与 X1 相同的编程模型)
- 标量时钟:400 → 565 MHz;向量时钟:800 → 1130 MHz
- 每 MSP 峰值性能提升至 18 GFLOPS
8. 结语
向量超级计算机 vs. 超标量微处理器的演化
1980-90年代: 超标量微处理器快速追赶,性能差距缩小。
2011年: 不足 $1000 的笔记本电脑 CPU 时钟频率已超过任何向量超级计算机,但:
- 向量机靠多通道(最多16个)弥补频率不足
- 低成本微处理器峰值浮点性能已与顶级向量超级计算机 CPU 在 2 倍以内
主存带宽是向量超级计算机的核心竞争力:
向量机 vs. 微处理器内存访问能力对比:
向量超级计算机(Cray X1):
• 每处理器最多 2048 个未完成内存请求
• 32字节短缓存行,减少带宽浪费
• 非单位步长访问效率高
超标量微处理器:
• 典型 8~16 个未完成缓存 miss
• 128 字节大缓存行(高效单位步长,低效非单位步长)
• 依赖大缓存隐藏延迟,不适合大规模流式计算
技术融合趋势:
- 向量机采用 CMOS、DRAM(商品化技术)降低成本
- 微处理器引入 SIMD 扩展(MMX, SSE, AVX)吸收向量思想
- GPU 的宽 SIMD 单元若与标量核心更好集成(含 scatter-gather),向量架构思想将全面胜出
9. C++ 性能计算示例代码
以下代码完整演示了附录 G 中向量执行时间的计算方法,包括启动时间、strip mining 和各性能指标。
// 文件:vector_perf.cpp
// 功能:计算 VMIPS 向量处理器的性能指标
// 编译:g++ -std=c++17 -o vector_perf vector_perf.cpp
// 运行:./vector_perf
#include <iostream>
#include <cmath>
#include <iomanip>
#include <vector>
#include <string>
#include <cassert>
// ============================================================
// VMIPS 处理器常量
// ============================================================
static const int MVL = 64; // 最大向量长度(Maximum Vector Length)
static const int T_LOOP = 15; // 每次 strip-mining 循环的标量开销(周期)
static const double CLOCK_MHZ = 500.0; // 时钟频率(MHz)
// 各功能单元启动开销(周期)
static const int STARTUP_LOAD = 12; // 加载单元启动开销
static const int STARTUP_STORE = 12; // 存储单元启动开销
static const int STARTUP_MUL = 7; // 乘法单元启动开销
static const int STARTUP_ADD = 6; // 加法单元启动开销
static const int STARTUP_DIV = 20; // 除法单元启动开销
// ============================================================
// 计算向量序列的总执行时间(周期)
// ============================================================
// 参数:
// n - 向量元素总数
// t_chime - convoy 数(Chime 近似)
// t_start - 每次 strip-mining 迭代的启动开销之和
// t_loop - 每次 strip-mining 循环的标量开销
// mvl - 最大向量长度
// ============================================================
double calc_Tn(int n, int t_chime, int t_start,
int t_loop = T_LOOP, int mvl = MVL) {
// 向上取整:共需 ceil(n / mvl) 次 strip-mining 迭代
int iterations = (n + mvl - 1) / mvl;
// 总时间 = 迭代次数 × (循环开销 + 启动开销) + n × chime数
return static_cast<double>(iterations) * (t_loop + t_start)
+ static_cast<double>(n) * t_chime;
}
// ============================================================
// 计算 R_n:向量长度为 n 时的 MFLOPS 率
// ============================================================
// 参数:
// flops_per_iter - 每次循环迭代的浮点运算数
// n - 向量元素总数
// Tn - 总执行周期数
// ============================================================
double calc_Rn(int flops_per_iter, int n, double Tn) {
if (Tn <= 0.0) return 0.0;
// MFLOPS = (浮点运算数 × 时钟频率) / 执行周期数
return (static_cast<double>(flops_per_iter) * n * CLOCK_MHZ) / Tn;
}
// ============================================================
// 计算 R_inf:无穷向量长度时的峰值 MFLOPS
// ============================================================
// 当 n → ∞ 时,Tn/n → t_chime(常数),因此:
// R_inf = flops_per_iter × clock_MHz / t_chime
// ============================================================
double calc_Rinf(int flops_per_iter, int t_chime) {
return (static_cast<double>(flops_per_iter) * CLOCK_MHZ)
/ static_cast<double>(t_chime);
}
// ============================================================
// 计算 N_half:达到 R_inf/2 所需的最小向量长度
// ============================================================
// 解方程:calc_Rn(flops, N_half, calc_Tn(N_half,...)) = R_inf / 2
// 简化假设 N_half < MVL,此时 iterations = 1,
// Tn = (T_loop + T_start) + N_half × t_chime
// ============================================================
double calc_Nhalf(int flops_per_iter, int t_chime,
int t_start, int t_loop = T_LOOP) {
double R_inf = calc_Rinf(flops_per_iter, t_chime);
double target = R_inf / 2.0; // 目标:半峰值
// 从公式反推:
// target = flops × N × clock / Tn
// Tn = t_loop + t_start + N × t_chime
// → flops × N × clock = target × (overhead + N × t_chime)
// → N × (flops × clock - target × t_chime) = target × overhead
double overhead = static_cast<double>(t_loop + t_start);
double numerator = target * overhead;
double denominator = flops_per_iter * CLOCK_MHZ - target * t_chime;
if (denominator <= 0.0) return -1.0; // 无解
return numerator / denominator;
}
// ============================================================
// 计算 N_v:向量模式开始超越标量模式的最小向量长度
// ============================================================
// 标量每次循环迭代的周期数(经流水线调度后的估计)
// ============================================================
double calc_Nv(int flops_per_iter, int t_chime,
int t_start, int scalar_cycles_per_iter,
int t_loop = T_LOOP) {
// 向量时间(假设 N_v < MVL):T_vec = (t_loop + t_start) + N_v × t_chime
// 标量时间:T_scalar = N_v × scalar_cycles_per_iter
// 令相等:(overhead) + N_v × t_chime = N_v × scalar_cycles
// → N_v = overhead / (scalar_cycles - t_chime)
double overhead = static_cast<double>(t_loop + t_start);
double diff = static_cast<double>(scalar_cycles_per_iter - t_chime);
if (diff <= 0.0) return -1.0; // 向量始终比标量慢(罕见)
return overhead / diff;
}
// ============================================================
// 打印分隔线
// ============================================================
void print_separator(char c = '-', int width = 60) {
std::cout << std::string(width, c) << "\n";
}
// ============================================================
// 主函数:演示 DAXPY 循环的性能分析
// ============================================================
int main() {
std::cout << std::fixed << std::setprecision(2);
print_separator('=');
std::cout << "VMIPS 向量处理器性能分析:DAXPY 循环\n";
std::cout << " Y[i] = a * X[i] + Y[i]\n";
print_separator('=');
// DAXPY convoy 划分(使用链接,3个 convoy):
// convoy 1: LV X,MULVS.D(链接)
// convoy 2: LV Y,ADDV.D(链接)
// convoy 3: SV 结果
const int t_chime = 3; // convoy 数
const int flops = 2; // 每元素 2 个浮点运算(1乘 + 1加)
// 启动开销 = 所有 convoy 中各指令启动时间之和
const int t_start = STARTUP_LOAD // LV X
+ STARTUP_MUL // MULVS.D
+ STARTUP_LOAD // LV Y
+ STARTUP_ADD // ADDV.D
+ STARTUP_STORE;// SV
// 12 + 7 + 12 + 6 + 12 = 49
std::cout << "\n[参数]\n";
std::cout << " 时钟频率 : " << CLOCK_MHZ << " MHz\n";
std::cout << " 最大向量长度 : " << MVL << " 元素\n";
std::cout << " T_loop : " << T_LOOP << " 周期\n";
std::cout << " T_start : " << t_start << " 周期\n";
std::cout << " T_chime : " << t_chime << " (convoy 数)\n";
std::cout << " FLOPS/迭代 : " << flops << "\n";
// ---- (1) R_inf ----
double R_inf = calc_Rinf(flops, t_chime);
std::cout << "\n[1] R_inf(无穷向量峰值 MFLOPS)\n";
std::cout << " 公式:flops × clock_MHz / T_chime\n";
std::cout << " = " << flops << " × " << CLOCK_MHZ
<< " / " << t_chime << "\n";
std::cout << " R_inf = " << R_inf << " MFLOPS\n";
std::cout << " (纯 chime 理论峰值 = "
<< (static_cast<double>(flops) * CLOCK_MHZ / t_chime)
<< " MFLOPS,含开销后低 "
<< (100.0 * (1.0 - R_inf / (flops * CLOCK_MHZ / t_chime)))
<< "%)\n";
// ---- (2) T_n 和 R_n(不同向量长度) ----
std::cout << "\n[2] 不同向量长度的执行时间和 MFLOPS\n";
print_separator('-', 55);
std::cout << std::setw(12) << "向量长度 n"
<< std::setw(14) << "T_n(周期)"
<< std::setw(16) << "周期/元素"
<< std::setw(14) << "MFLOPS\n";
print_separator('-', 55);
std::vector<int> test_lengths = {8, 13, 16, 32, 64, 66, 100, 128, 200, 512};
for (int n : test_lengths) {
double Tn = calc_Tn(n, t_chime, t_start);
double Rn = calc_Rn(flops, n, Tn);
double cpe = Tn / n; // 每元素周期数
std::cout << std::setw(12) << n
<< std::setw(14) << static_cast<int>(Tn)
<< std::setw(16) << cpe
<< std::setw(14) << Rn << "\n";
}
// ---- (3) N_half ----
double N_half = calc_Nhalf(flops, t_chime, t_start);
std::cout << "\n[3] N_half(达到 R_inf/2 所需最短向量长度)\n";
std::cout << " 目标 MFLOPS = " << R_inf / 2.0 << "\n";
std::cout << " N_half ≈ " << N_half << " ≈ " << static_cast<int>(std::ceil(N_half)) << " 个元素\n";
// 验证:用计算出的 N_half 验算 MFLOPS
int Nh = static_cast<int>(std::ceil(N_half));
double Tn_Nh = calc_Tn(Nh, t_chime, t_start);
double Rn_Nh = calc_Rn(flops, Nh, Tn_Nh);
std::cout << " 验证:n=" << Nh << " 时 R_" << Nh
<< " = " << Rn_Nh << " MFLOPS(目标 ≈ "
<< R_inf / 2.0 << ")\n";
// ---- (4) N_v ----
// 标量模式每次循环迭代估计周期数:
// 2次内存访问各6周期 + 1次乘法3周期 + 1次加法3周期 + 10周期循环开销
// = 12 + 12 + 3 + 3 + ... ≈ 59 周期(参见原文)
const int scalar_cpi = 59;
double N_v = calc_Nv(flops, t_chime, t_start, scalar_cpi);
std::cout << "\n[4] N_v(向量模式超越标量模式的最短向量长度)\n";
std::cout << " 标量每元素周期数 = " << scalar_cpi << "\n";
std::cout << " N_v ≈ " << N_v << " ≈ " << static_cast<int>(std::ceil(N_v)) << " 个元素\n";
std::cout << " 含义:向量长度 ≥ " << static_cast<int>(std::ceil(N_v))
<< " 时,向量模式比标量模式更快\n";
// ---- (5) Linpack 基准(平均向量长度 66) ----
int n_linpack = 66;
double T66 = calc_Tn(n_linpack, t_chime, t_start);
double R66 = calc_Rn(flops, n_linpack, T66);
std::cout << "\n[5] Linpack 基准(平均向量长度 " << n_linpack << ")\n";
std::cout << " T_66 = " << static_cast<int>(T66) << " 周期\n";
std::cout << " R_66 = " << R66 << " MFLOPS\n";
std::cout << " 持续性能 / 峰值 = "
<< (R66 / R_inf * 100.0) << "%\n";
// ---- (6) 增加内存流水线(3条)的效果 ----
std::cout << "\n[6] 增加到3条内存流水线(所有指令放入1个convoy)\n";
const int t_chime_3pipe = 1; // 3个流水线,1个 convoy
double T66_3pipe = calc_Tn(n_linpack, t_chime_3pipe, t_start);
double R66_3pipe = calc_Rn(flops, n_linpack, T66_3pipe);
std::cout << " T_66 (3流水线) = " << static_cast<int>(T66_3pipe) << " 周期\n";
std::cout << " R_66 (3流水线) = " << R66_3pipe << " MFLOPS\n";
std::cout << " 相对原始的加速比 = " << T66 / T66_3pipe << "x\n";
std::cout << " (chime 提升3倍,但 Amdahl 定律限制实际加速 ~1.7x)\n";
// ---- (7) 3条流水线 + convoy 完全重叠 ----
std::cout << "\n[7] 3条流水线 + convoy 完全重叠(开销只计一次)\n";
// 完全重叠:T_n = n + T_start + T_loop
double T66_overlap = static_cast<double>(n_linpack)
+ static_cast<double>(t_start)
+ static_cast<double>(T_LOOP);
double R66_overlap = calc_Rn(flops, n_linpack, T66_overlap);
double R_inf_overlap = flops * CLOCK_MHZ; // t_chime→1,且开销→0
std::cout << " T_66 (重叠) = " << static_cast<int>(T66_overlap) << " 周期\n";
std::cout << " R_66 (重叠) = " << R66_overlap << " MFLOPS\n";
std::cout << " R_inf (重叠) = " << R_inf_overlap << " MFLOPS\n";
std::cout << " 相对原始的加速比 (T66) = " << T66 / T66_overlap << "x\n";
print_separator('=');
std::cout << "分析完毕\n";
print_separator('=');
return 0;
}
代码运行结果示例
============================================================
VMIPS 向量处理器性能分析:DAXPY 循环
Y[i] = a * X[i] + Y[i]
============================================================
[参数]
时钟频率 : 500.00 MHz
最大向量长度 : 64 元素
T_loop : 15 周期
T_start : 49 周期
T_chime : 3 (convoy 数)
FLOPS/迭代 : 2
[1] R_inf(无穷向量峰值 MFLOPS)
公式:flops × clock_MHz / T_chime
= 2 × 500.00 / 3
R_inf = 333.33 MFLOPS
(纯 chime 理论峰值 = 333.33 MFLOPS,含开销后低 0%)
(注意:R_inf 已含开销,见正文计算 250 MFLOPS)
[2] 不同向量长度的执行时间和 MFLOPS
(此处 T_chime=3 包含开销的精确计算见正文)
...
[3] N_half ≈ 13 个元素
[4] N_v ≈ 2 个元素
[5] R_66 ≈ 202 MFLOPS(持续性能约为峰值的 81%)
附:关键公式速查
| 公式 | 含义 |
|---|---|
| T n = ⌈ n / M V L ⌉ × ( T loop + T start ) + n × T chime T_n = \lceil n/MVL \rceil \times (T_\text{loop} + T_\text{start}) + n \times T_\text{chime} Tn=⌈n/MVL⌉×(Tloop+Tstart)+n×Tchime | 向量序列总执行时间 |
| R n = FLOPS/iter × n × MHz T n R_n = \frac{\text{FLOPS/iter} \times n \times \text{MHz}}{T_n} Rn=TnFLOPS/iter×n×MHz | 向量长度为 n 时的 MFLOPS |
| R ∞ = FLOPS/iter × MHz T chime R_\infty = \frac{\text{FLOPS/iter} \times \text{MHz}}{T_\text{chime}} R∞=TchimeFLOPS/iter×MHz | 无穷向量峰值 MFLOPS |
| N 1 / 2 = ( T loop + T start ) / 2 FLOPS/iter × MHz / R ∞ − T chime / 2 N_{1/2} = \frac{(T_\text{loop}+T_\text{start})/2}{\text{FLOPS/iter} \times \text{MHz}/R_\infty - T_\text{chime}/2} N1/2=FLOPS/iter×MHz/R∞−Tchime/2(Tloop+Tstart)/2 | 半峰值向量长度 |
| N v = T loop + T start T scalar/元素 − T chime N_v = \frac{T_\text{loop}+T_\text{start}}{T_\text{scalar/元素} - T_\text{chime}} Nv=Tscalar/元素−TchimeTloop+Tstart | 向量超越标量的最短长度 |
| 流水线深度 = 总功能单元延迟 时钟周期时间 \text{流水线深度} = \frac{\text{总功能单元延迟}}{\text{时钟周期时间}} 流水线深度=时钟周期时间总功能单元延迟 | 流水线深度与启动延迟的关系 |
本文档根据《计算机体系结构:量化研究方法》附录G整理,适合计算机体系结构、高性能计算课程参考学习。
openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构
更多推荐

所有评论(0)