并行体系结构
1. Flynn 分类法
Michael Flynn(1966)根据指令流和数据流的并行度将计算机体系结构分为四类:
graph TB
subgraph "Flynn's Taxonomy"
direction TB
A["<b>SISD</b><br/>Single Instruction<br/>Single Data<br/>经典单核 CPU"]
B["<b>SIMD</b><br/>Single Instruction<br/>Multiple Data<br/>向量处理 / GPU核心"]
C["<b>MISD</b><br/>Multiple Instruction<br/>Single Data<br/>容错管道(罕见)"]
D["<b>MIMD</b><br/>Multiple Instruction<br/>Multiple Data<br/>多核 / 分布式"]
end
style A fill:#e1f5fe
style B fill:#e8f5e9
style C fill:#fff3e0
style D fill:#f3e5f5
| 类别 | 指令流 | 数据流 | 代表 |
|---|---|---|---|
| SISD | 单 | 单 | 经典单核处理器 |
| SIMD | 单 | 多 | SSE/AVX、NEON、GPU 核心 |
| MISD | 多 | 单 | 航天容错系统(极罕见) |
| MIMD | 多 | 多 | 多核 CPU、集群、超算 |
2. SIMD 并行
2.1 向量扩展(CPU SIMD)
同一条指令对多个数据元素同时操作:
| 扩展 | 宽度 | 架构 |
|---|---|---|
| SSE | 128-bit(4×float) | x86 |
| AVX2 | 256-bit(8×float) | x86 |
| AVX-512 | 512-bit(16×float) | x86(服务器) |
| NEON | 128-bit | ARM |
| SVE/SVE2 | 128-2048 bit(可变) | ARMv9 |
| RVV | 可变(VLEN) | RISC-V |
加速比上限(忽略标量开销):
\[
S_{\text{SIMD}} = \frac{\text{Vector Width (bits)}}{\text{Element Width (bits)}}
\]
例如 AVX-512 处理 32-bit float:\(S = 512 / 32 = 16\times\)。
2.2 SIMD 编程模型
- 编译器自动向量化:编译器检测可向量化的循环
- Intrinsics:手动调用 SIMD 指令(如
_mm256_add_ps) - 库抽象:高层库封装(如 Eigen、xsimd)
3. 多核与共享内存
3.1 内存架构
graph TB
subgraph "UMA (Uniform Memory Access)"
C1[Core 0] --> BUS1[总线/交叉开关]
C2[Core 1] --> BUS1
C3[Core 2] --> BUS1
BUS1 --> MEM1[共享内存<br/>统一延迟]
end
subgraph "NUMA (Non-Uniform Memory Access)"
C4[Core 0] --> L1[本地内存<br/>快]
C5[Core 1] --> L1
C6[Core 2] --> L2[本地内存<br/>快]
C7[Core 3] --> L2
L1 <-->|互联<br/>慢| L2
end
| 架构 | 特点 | 适用规模 |
|---|---|---|
| UMA | 所有核心访存延迟相同 | 小规模(≤8 核) |
| NUMA | 本地内存快,远端内存慢 | 多路服务器(数十至数百核) |
NUMA 感知编程至关重要——数据应尽量分配在使用它的核心的本地节点上。
3.2 缓存一致性(Cache Coherence)
多核共享内存时,各核心私有 cache 中同一数据可能不一致。
一致性要求:
- 写传播(Write Propagation):一个核心的写操作最终对所有核心可见
- 写串行化(Write Serialization):对同一地址的写操作,所有核心观察到相同顺序
3.3 MESI 协议
最经典的监听协议(Snooping Protocol),每个 cache line 有 4 种状态:
| 状态 | 含义 |
|---|---|
| M(Modified) | 已修改,仅本 cache 有效,主存过期 |
| E(Exclusive) | 未修改,仅本 cache 有效,与主存一致 |
| S(Shared) | 未修改,可能多个 cache 持有副本 |
| I(Invalid) | 无效,不可使用 |
状态转换示例:
stateDiagram-v2
[*] --> I
I --> E: 本地读 (无其他副本)
I --> S: 本地读 (有其他副本)
E --> M: 本地写
S --> M: 本地写 (其他副本→I)
M --> S: 远端读 (写回主存)
M --> I: 远端写
E --> I: 远端写
S --> I: 远端写
E --> S: 远端读
MOESI / MESIF 扩展:
- MOESI(AMD):增加 Owned 状态,避免脏数据必须写回主存
- MESIF(Intel):增加 Forward 状态,指定由谁响应共享请求
3.4 目录协议(Directory Protocol)
监听协议在大规模系统中广播开销大。目录协议使用集中式目录记录每个 cache line 的共享者信息:
- 适合 NUMA 架构和大规模多核(>16 核)
- 点对点通信,无需广播
- 目录条目:状态 + 共享者位向量
3.5 伪共享(False Sharing)
不同核心访问不同变量,但这些变量恰好位于同一 cache line,导致频繁的一致性失效。
\[
\text{性能退化} \propto \text{一致性消息频率}
\]
解决方案:
- Cache line 对齐(padding)
- 每线程独立的数据结构(thread-local)
alignas(64)确保独立变量在不同 cache line
4. GPU 计算模型
4.1 GPU vs CPU 设计哲学
graph LR
subgraph "CPU: 延迟优化"
A1[大 Cache] --- A2[复杂控制逻辑]
A2 --- A3[少量强核心]
A3 --- A4[乱序执行]
end
subgraph "GPU: 吞吐量优化"
B1[小 Cache] --- B2[简单控制逻辑]
B2 --- B3[海量弱核心]
B3 --- B4[线程级并行隐藏延迟]
end
| 特性 | CPU | GPU |
|---|---|---|
| 核心数 | 4-128(强核) | 数千-数万(CUDA 核心) |
| 控制逻辑 | 复杂(乱序、分支预测) | 简单(顺序执行) |
| Cache | 大(L1+L2+L3 可达 100MB) | 小(主要依赖寄存器和共享内存) |
| 内存带宽 | ~50-100 GB/s | ~1-3 TB/s(HBM) |
| 适合工作负载 | 串行、分支密集 | 大规模数据并行 |
4.2 SIMT(Single Instruction Multiple Thread)
NVIDIA GPU 的执行模型:
- 线程(Thread):最小执行单元,有独立寄存器和 PC
- Warp:32 个线程组成一个 warp,锁步执行同一指令
- 线程块(Block):多个 warp,共享 Shared Memory,在同一 SM 上执行
- 网格(Grid):多个 Block,跨 SM 分配
Grid
├── Block (0,0)
│ ├── Warp 0: Thread 0-31
│ ├── Warp 1: Thread 32-63
│ └── ...
├── Block (0,1)
│ └── ...
└── ...
4.3 SM(Streaming Multiprocessor)结构
每个 SM 包含:
| 组件 | 数量(典型) | 功能 |
|---|---|---|
| CUDA 核心(FP32) | 128 | 浮点/整数运算 |
| Tensor 核心 | 4 | 矩阵乘加(AI 加速) |
| Load/Store 单元 | 32 | 访存 |
| SFU(特殊函数) | 16 | sin, cos, exp 等 |
| Warp 调度器 | 4 | 每周期选择就绪 warp 发射 |
| 寄存器文件 | 256 KB | 线程私有寄存器 |
| Shared Memory | 64-228 KB | Block 内线程共享 |
4.4 分支分化(Warp Divergence)
当 warp 内线程走不同分支路径时:
- 必须串行执行每条路径(mask 掉不参与的线程)
- 最差情况:两条路径都执行,吞吐量减半
\[
\text{效率} = \frac{\text{活跃线程数}}{32}
\]
优化:减少 warp 内的分支分化,将相同路径的线程分到同一 warp。
4.5 内存层次
| 内存类型 | 作用域 | 延迟 | 带宽 |
|---|---|---|---|
| 寄存器 | 线程私有 | 1 cycle | 最高 |
| Shared Memory | Block 内共享 | ~5 cycles | 很高 |
| L1 Cache | SM 级 | ~30 cycles | 高 |
| L2 Cache | 全局 | ~200 cycles | 中等 |
| Global Memory (HBM) | 全局 | ~300-500 cycles | ~3 TB/s |
关键优化:利用 Shared Memory 作为手动管理的缓存,减少全局内存访问。
5. 互联拓扑
5.1 片上互联
| 拓扑 | 结构 | 适用规模 |
|---|---|---|
| 总线(Bus) | 共享介质 | ≤4 核 |
| 交叉开关(Crossbar) | 全连接 | ≤16 核(面积 \(O(N^2)\)) |
| 环形(Ring) | 单/双环 | Intel 消费级多核 |
| Mesh | 二维网格 | 众核(Intel Xeon, GPU) |
5.2 片间互联
| 互联 | 特点 | 用途 |
|---|---|---|
| PCIe | 通用、中等带宽 | CPU-GPU、CPU-SSD |
| NVLink | 超高带宽(900 GB/s) | GPU-GPU(NVIDIA) |
| CXL | 缓存一致性扩展 | CPU-加速器、内存池化 |
| Infinity Fabric | AMD 片间互联 | Chiplet 连接 |
| UCIe | 开放 Chiplet 互联标准 | 异构 Chiplet |
6. 并行编程模型
| 模型 | 抽象级别 | 适用硬件 |
|---|---|---|
| Pthreads / std::thread | 低级线程 | 多核 CPU |
| OpenMP | 编译指导(#pragma) |
多核 CPU |
| CUDA / HIP | GPU 并行编程 | NVIDIA / AMD GPU |
| SYCL / oneAPI | 异构并行(跨设备) | CPU + GPU + FPGA |
| MPI | 消息传递(分布式内存) | 集群、超算 |
7. 性能指标与分析
7.1 加速比与效率
\[
S(p) = \frac{T(1)}{T(p)}, \quad E(p) = \frac{S(p)}{p}
\]
- 理想线性加速比:\(S(p) = p\),\(E(p) = 1\)
- 实际受限于:串行部分(Amdahl)、通信开销、负载不均衡
7.2 Roofline 模型
衡量程序受计算还是带宽限制:
\[
\text{Performance} = \min(\text{Peak FLOPS}, \text{Bandwidth} \times \text{Operational Intensity})
\]
其中操作强度 \(\text{OI} = \text{FLOPs} / \text{Bytes transferred}\)。
- \(\text{OI} < \text{Ridge Point}\):带宽受限(memory-bound)
- \(\text{OI} > \text{Ridge Point}\):计算受限(compute-bound)
优化策略
- Memory-bound:提升数据复用、预取、压缩、使用更高带宽内存
- Compute-bound:利用 SIMD、Tensor 核心、算法优化减少运算量
8. 异构计算与领域专用架构
8.1 异构 SoC
现代 SoC 集成多种计算单元:
| 单元 | 擅长任务 |
|---|---|
| CPU 大核 | 串行、控制密集 |
| CPU 小核 | 低功耗后台任务 |
| GPU | 图形、大规模数据并行 |
| NPU/TPU | 神经网络推理 |
| DSP | 信号处理 |
| ISP | 图像处理 |
8.2 领域专用加速器
| 加速器 | 应用 | 关键特点 |
|---|---|---|
| Google TPU | 机器学习 | 脉动阵列、BF16/INT8 |
| NVIDIA Tensor Core | 矩阵乘加 | FP16/BF16/INT8/FP8 |
| Apple Neural Engine | 端侧 AI 推理 | 高能效 |
| FPGA | 可重构计算 | 灵活但开发难度高 |