CPU 流水线、乱序执行与分支预测
为什么不等一条指令完全结束
若取指、译码、执行和访存各占一个阶段,完全串行会让多数部件等待。流水线让多条指令处于不同阶段:
cycle 1 2 3 4 5
inst A Fetch Decode Execute Memory Retire
inst B Fetch Decode Execute Memory ...
inst C Fetch Decode Execute ...延迟表示一条指令从开始到结果可用的时间;吞吐表示单位时间完成多少指令。流水线主要提高吞吐,不会让单条依赖链凭空没有延迟。
三类依赖
A: r1 = load [p]
B: r2 = r1 + 1 RAW:B 真正依赖 A
C: r3 = x * y 与 A/B 独立,可提前执行
D: if (r2) goto L 控制依赖:下一条地址未知- 数据依赖使消费者必须等待生产者;
- 内存别名可能让 CPU/编译器无法确认 load 与 store 是否冲突;
- 分支使取指前端暂时不知道下一条路径。
超标量与乱序执行
常见现代 CPU 每周期可以译码、发射和执行多条微操作。乱序核心在不改变单线程可观察结果的前提下,让独立工作越过正在等待的指令。
program order: A(load miss) -> B(depends A) -> C(independent) -> D
issue order: A(waiting) -> C -> D -> B(after A ready)
retire order: A -> B -> C -> D典型组成包括:
- register renaming:消除名字造成的假依赖;
- reservation stations / scheduler:等待操作数并选择可执行工作;
- execution ports:整数、浮点、load/store 等资源;
- reorder buffer(ROB):按程序顺序退休并支持精确异常。
这些名称和组织来自常见微架构,不是 ISO C++ 或所有 ISA 强制的固定布局。
分支预测与推测执行
前端预测分支方向和目标,沿预测路径继续取指。预测正确时隐藏控制等待;预测错误时丢弃错误路径的未退休结果并恢复正确状态。
predict taken -> execute future work -> verify branch
| correct: retire
+ wrong: flush and restart可观察的体系结构状态会恢复,但推测可能影响 Cache 等微架构状态,这也是 Spectre 类侧信道的背景。安全问题不能简单表述为“CPU 执行了错误代码”。
SMT 不是双倍核心
同时多线程(如 Intel Hyper-Threading)让一个物理核心保存多个线程的体系结构状态,共享前端、执行端口和 Cache 等资源。它能填补一条线程的等待空隙,但两个硬件线程会竞争资源,因此收益取决于工作负载。
对代码的启示
- 缩短长依赖链;
- 让数据连续,减少 Cache miss;
- 避免不可预测且工作很小的分支;
- 不为“无分支”写难以维护的技巧,先测量;
- 编译器可能进行 if-conversion、展开和向量化,源码外观不等于机器执行。
深入原理与工程实践
前面的内容负责建立统一心智模型;下面把同一主题继续拆到执行过程、代码、性能代价与工程判断。
<!-- migrated-deep-dive:start -->
完整迁入:原硬件学习地图与 CPU 执行主线
硬件编程与高性能计算:一张可走通的学习地图 / A Practical Learning Map for Hardware Programming and HPC
📅 创建时间:2026-06-02 🏷️ 标签:#Hardware #HPC #CUDA #GPU #并行计算 📚 前置知识:C++ 基础(会写类、指针、STL 即可) 📚 相关知识:[[/04-ai/01-llm-engineering/09-transformer-training-computation]] [[/04-ai/01-llm-engineering/10-transformer-inference-computation]] [[/03-web/05-backend-engineering/08-concurrency]]
文档目标
如果 CUDA、GPU、Cache、SIMD、MPI 像一堆互不相干的缩写,先记住:它们都在解决同一件事——让数据更快地完成计算。
程序的耗时,大致可以拆成三部分:
总时间 = 计算时间 + 搬运数据的时间 + 相互等待的时间容量则是另一条硬约束:数据装不下,再快的计算单元也无法直接工作。整套笔记就是沿着计算、存储、通信、容量四条线,理解从单核 CPU 到 GPU 集群的性能问题。
详细的分层阅读方法和生活化类比见 目录首页。第一次阅读不要求吃透所有公式、参数和代码。
学完后你应该能回答
- 为什么同一段循环,顺序访问通常比随机访问快?
- 为什么 GPU 核心很多,却不适合所有任务?
- 为什么用了 8 张 GPU,训练通常不会正好快 8 倍?
- 为什么优化前要先判断瓶颈在计算、访存还是等待?
- 为什么模型参数看似能装进显存,训练时仍可能 OOM?
两个例子贯穿全部章节
先用向量相加理解机制,再用大模型训练理解规模:
向量相加(建立直觉) 大模型训练(落到真实系统)
│ │
├─ 如何拆成独立任务 ├─ 参数和激活能否装下
├─ 线程如何分工 ├─ 多卡如何分工与同步
├─ 数据放在哪里 ├─ CPU 能否及时供给数据
└─ 时间耗在哪里 └─ 如何测量真实瓶颈文中的“单卡训练 175B 模型 30 天”是用于串联概念的假设场景,不是严谨的可运行基线。单卡首先会遇到容量约束;不同精度、优化器和并行策略也会显著改变时间。学习时要把它当作问题引子,而不是性能结论。
章节之间的因果关系
程序为什么慢?
├─ 单个处理器如何工作? → 01 CPU、Cache、内存层次
├─ 工作能拆开同时做吗? → 02 并行上限与效率
├─ GPU 如何执行这些工作? → 03 GPU 架构 → 04 CUDA 线程模型
├─ 数据如何及时送到计算单元? → 05 内存 → 06 优化 → 11 测量工具
├─ 一台机器还不够怎么办? → 07 CPU 并行 → 08 多机多卡 → 09 协同
└─ 怎样用于 AI 训练? → 10 训练优化 → 12 NPU → 13 趋势建议实际按 01 → 02 → 03 → 04 → 05 → 11 → 06 → 07 → 09 → 08 → 10 阅读。性能分析放在优化之前,是为了先找到问题再选手段。
技术栈总览
┌─────────────────────────────────────────────────────────────────┐
│ 硬件编程技术栈 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 应用层 │
│ ┌────────────┐ ┌────────────┐ ┌────────────┐ │
│ │ PyTorch │ │ TensorRT │ │ DeepSpeed │ │
│ │ (框架层) │ │ (推理优化) │ │ (分布式) │ │
│ └────────────┘ └────────────┘ └────────────┘ │
│ │
│ 编程层 │
│ ┌────────────┐ ┌────────────┐ ┌────────────┐ │
│ │ CUDA C++ │ │ OpenMP │ │ MPI │ │
│ │ (.cu) │ │ (#pragma) │ │ (集群通信)│ │
│ └────────────┘ └────────────┘ └────────────┘ │
│ │
│ 硬件层 │
│ ┌────────────┐ ┌────────────┐ ┌────────────┐ │
│ │ GPU │ │ CPU 多核 │ │ HPC 集群 │ │
│ │ (NVIDIA) │ │ (x86/ARM) │ │ (多节点) │ │
│ └────────────┘ └────────────┘ └────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘给 C++ 攻城狮的 CUDA 工具链入门
本节专门为只学过 C++ 的你准备。如果你只会 g++ main.cpp -o app,下面的内容让你从零搞懂 CUDA 项目的编译流程。
什么是 nvcc?
nvcc 是 NVIDIA CUDA 编译器(NVIDIA CUDA Compiler)。它的作用类似 g++,但专门用于编译 CUDA 代码。
g++ main.cpp -o app # C++ 代码 → CPU 可执行文件
nvcc main.cu -o app # CUDA 代码 → GPU 可执行文件nvcc 实际上是一套工具链的入口,它会:
- 分离代码中的 CPU 部分和 GPU 部分
- 用
g++编译 CPU 部分 - 用
ptxas(NVIDIA 的汇编器)编译 GPU 部分 - 用
gold(链接器)合并两者
nvcc 的安装
### Windows:安装 CUDA Toolkit
### https://developer.nvidia.com/cuda-downloads
### 下载后一路 Next,默认安装到:
C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v12.x
### 安装完成后,命令行验证:
nvcc --version
### 输出类似:
### nvcc: NVIDIA (R) Cuda compiler driver
### Cuda compilation tools, release 12.4, V12.4.131第一个 CUDA 项目的文件结构
my_cuda_project/
├── main.cpp ← C++ 代码:CPU 端(Host),负责调度和数据传输
├── vector_add.cu ← CUDA 代码:GPU 端(Device),具体计算逻辑
├── vector_add.cuh ← CUDA 头文件:kernel 函数声明
└── CMakeLists.txt ← 编译配置(用 CMake 代替直接调用 nvcc)为什么分成 .cpp 和 .cu? CUDA 代码混合了 CPU 代码和 GPU 代码,但:
.cpp文件:纯 CPU 代码,用g++编译.cu文件:GPU 代码,用nvcc编译nvcc能同时处理两者,但你也可以分开编译后链接
编译方式一:直接用 nvcc
### 最简单的编译方式(一行命令搞定)
nvcc vector_add.cu main.cpp -o vector_add
### 指定 GPU 架构(可选,加了编译更精确)
nvcc vector_add.cu main.cpp -o vector_add \
--generate-code arch=compute_80,code=sm_80
### 参数解释:
### --generate-code arch=compute_80,code=sm_80
### compute_80 = 虚拟架构(PTX 汇编级别)
### sm_80 = 真实架构(A100 的 compute capability 是 8.0)
### 简单理解为:告诉 nvcc 你的 GPU 是什么型号编译方式二:用 CMake(推荐)
实际项目中都用 CMake 管理大型项目:
### CMakeLists.txt
cmake_minimum_required(VERSION 3.18)
project(my_cuda_project LANGUAGES CXX CUDA)
### 查找 CUDA
find_package(CUDAToolkit REQUIRED)
### 你的源文件
add_executable(vector_add main.cpp vector_add.cu)
### 链接 CUDA 库
target_link_libraries(vector_add PRIVATE CUDA::cudart)mkdir build
cd build
cmake ..
cmake --build . --config Release
./vector_add常用 nvcc 编译选项速查
nvcc main.cu -o app [选项]
常用选项:
-o <file> 输出文件名
-arch=sm_XX 指定 GPU 架构(如 sm_80 = A100)
-O0 / -O1 / -O2 / -O3 优化级别(默认 -O3)
-g 生成调试信息(gdb 可调)
-lineinfo 生成行号信息(cuda-gdb 可调)
--ptx 只输出 PTX 汇编,不生成最终二进制
--run 编译后直接运行(仅限简单程序)
-Xcompiler -Wall 把 g++ 的警告也显示出来
常见架构对照:
sm_70 = V100
sm_80 = A100
sm_86 = RTX 3090 / A5000
sm_89 = Orin
sm_90 = H100 / H200常见错误与排查
### 错误1:nvcc: command not found
### 原因:CUDA 没有加入 PATH
### 解决:
### Windows: 系统环境变量 → PATH → 添加
### C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v12.x\bin
### 错误2:unsupported GPU architecture
### 原因:nvcc 版本太老,不支持你的 GPU
### 解决:升级 CUDA Toolkit,或用 -arch=sm_XX 指定更低架构
### 错误3:undefined reference to 'cudaMalloc'
### 原因:没有链接 CUDA 运行时库
### 解决:target_link_libraries(... CUDA::cudart)
### 错误4:kernel launch failed: no kernel image is available
### 原因:编译时指定的 GPU 架构和运行时的 GPU 不匹配
### 解决:重新用正确的 -arch=sm_XX 编译开发工具推荐
┌──────────────────────────────────────────────────────────────┐
│ CUDA 开发工具推荐 │
├──────────────────────────────────────────────────────────────┤
│ │
│ IDE │
│ ✅ VS Code + NVIDIA Nsight VS Code Edition(免费) │
│ ✅ Visual Studio + Nsight Visual Studio Edition(功能最强) │
│ ✅ CLion(支持 CUDA 语法高亮) │
│ │
│ 命令行调试 │
│ ✅ cuda-gdb ← GPU 专用调试器 │
│ ✅ compute-sanitizer ← 内存错误检测(越界/未初始化/竞争) │
│ │
│ 性能分析 │
│ ✅ NVIDIA Nsight Systems ← 系统级 timeline 分析 │
│ ✅ NVIDIA Nsight Compute ← GPU kernel 级别性能分析 │
│ │
│ 在线实验(无需本地 GPU) │
│ ✅ Google Colab + 切换到 T4 GPU(免费,有时间限制) │
│ ✅ Lambda Lab / Vast.ai(租用 GPU 云服务器) │
│ │
└──────────────────────────────────────────────────────────────┘C++ 到 CUDA C++:最小的认知跨越
作为 C++ 程序员,你需要理解的核心变化只有一个:
┌────────────────────────────────────────────────────────────┐
│ │
│ C++:代码在 CPU 上运行 │
│ ↓ │
│ main() → 调用函数 → 函数在 CPU 上执行 │
│ │
│ CUDA C++:代码分为两部分 │
│ │
│ Host(C++):负责调度、数据传输、控制流 │
│ Device(CUDA):负责大规模并行计算 │
│ │
│ 你写的 .cu 文件 = Host 代码 + Device 代码(混合在一起) │
│ │
│ 最简单的理解: │
│ CUDA = C++ + 一套新的关键字(__global__, __device__ 等) │
│ + 一套新的运行时 API(cudaMalloc, cudaMemcpy 等) │
│ │
└────────────────────────────────────────────────────────────┘// main.cpp(C++,Host 端)
int main() {
// 申请 GPU 显存
float* d_a;
cudaMalloc(&d_a, N * sizeof(float));
// CPU 数据拷到 GPU
cudaMemcpy(d_a, h_a, N * sizeof(float), cudaMemcpyHostToDevice);
// 调用 GPU kernel(launch 一个 grid)
add<<<blocks, threads>>>(d_a, d_b, d_c);
// GPU 结果拷回 CPU
cudaMemcpy(h_c, d_c, N * sizeof(float), cudaMemcpyDeviceToHost);
// 释放显存
cudaFree(d_a);
}// vector_add.cu(CUDA C++,Device 端)
// __global__ = 在 GPU 上运行,可从 CPU 调用
__global__
void add(float* a, float* b, float* c, int N) {
int i = blockIdx.x * blockDim.x + threadIdx.x;
if (i < N) {
c[i] = a[i] + b[i];
}
}你不需要立刻理解上面每个细节——第 4 章会从头讲清楚。现在只需要记住:CUDA C++ 就是在 C++ 里加了 GPU 并行计算的能力。
硬件技术分层图谱
┌─────────────────────────────────────────────────────────────┐
│ 硬件编程技术分层 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 分布式训练层 │
│ ┌────────────┐ ┌────────────┐ ┌────────────┐ │
│ │ 数据并行 │ │ 模型并行 │ │ 流水线并行 │ │
│ │ (DDP/DeepS)│ │ (Megatron)│ │ (PipeDream)│ │
│ └────────────┘ └────────────┘ └────────────┘ │
│ │
│ 通信层 │
│ ┌────────────┐ ┌────────────┐ ┌────────────┐ │
│ │ NCCL │ │ NVLink │ │ InfiniBand │ │
│ │ (GPU间通信)│ │ (卡间直连) │ │ (节点间) │ │
│ └────────────┘ └────────────┘ └────────────┘ │
│ │
│ 编程框架层 │
│ ┌────────────┐ ┌────────────┐ ┌────────────┐ │
│ │ CUDA C++ │ │ OpenMP │ │ MPI │ │
│ │ (.cu) │ │ (#pragma) │ │ (集群) │ │
│ └────────────┘ └────────────┘ └────────────┘ │
│ │
│ GPU 硬件层 │
│ ┌────────────┐ ┌────────────┐ ┌────────────┐ │
│ │ 流式多处理器│ │ 全局/共享内存│ │ TensorCore │ │
│ │ (SM) │ │ (Global/ │ │ (矩阵加速) │ │
│ │ │ │ Shared) │ │ │ │
│ └────────────┘ └────────────┘ └────────────┘ │
│ │
│ CPU 硬件层 │
│ ┌────────────┐ ┌────────────┐ ┌────────────┐ │
│ │ 多级缓存 │ │ SIMD 单元 │ │ NUMA 拓扑 │ │
│ │ L1/L2/L3 │ │ AVX-512 │ │ (多路) │ │
│ └────────────┘ └────────────┘ └────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘AI 辅助 vs 必须深入原理
┌─────────────────────────────────────────────────────────────┐
│ AI 可查 vs 必须理解清单 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 可以高度依赖 AI(偏记忆/查表型): │
│ ✅ CUDA API 具体语法——AI 随时告诉你用什么函数 │
│ ✅ 特定 GPU 型号的参数——A100/H100 的 SM 数量/显存大小 │
│ ✅ CUDA .cu 文件的 kernel 模板——矩阵乘法代码框架 │
│ ✅ nvcc 编译选项——需要时查文档即可 │
│ ✅ NPU 厂商的 SDK 命令速查——昇腾 CANN API 列表 │
│ │
│ 必须深入理解原理的(决定你能否解决实际问题的): │
│ 🔴 阿姆达尔定律——知道并行优化的理论上限在哪里 │
│ 🔴 GPU 内存层次——为什么合并访问能提升 10 倍性能 │
│ 🔴 SIMT 执行模型——Warp 分支分化如何影响性能 │
│ 🔴 CUDA Stream 异步执行——如何让计算和通信重叠 │
│ 🔴 显存 OOM 的根因——百亿参数为什么放不下单卡 │
│ 🔴 NPU vs GPU 的生态差异——昇腾 CANN 和 CUDA 的本质区别 │
│ │
│ 原则:能查到的不用背,理解不了的面试必挂。 │
│ │
└─────────────────────────────────────────────────────────────┘学习路径与面试频率
┌─────────────────────────────────────────────────────────────┐
│ 面试频率 × 理解深度 矩阵 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 高面试频率 │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ CUDA 线程层次(Thread/Block/Grid) ⭐⭐⭐ 高理解 │ │
│ │ GPU vs CPU 架构差异 ⭐⭐⭐ 高理解 │ │
│ │ CUDA 内存模型(Global/Shared/Register)⭐⭐⭐ 高理解 │ │
│ │ 阿姆达尔定律 + 加速比 ⭐⭐⭐ 高理解 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ 中面试频率 │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ SIMD 向量化(AVX-512/SVE) ⭐⭐ 中理解 │ │
│ │ OpenMP 并行编程 ⭐⭐ 中理解 │ │
│ │ MPI/NCCL 通信原语 ⭐⭐ 中理解 │ │
│ │ 混合精度训练(FP16/BF16) ⭐⭐ 中理解 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ 低面试频率(但要会用) │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ NPU 生态(昇腾/寒武纪/TPU) ⭐ 理解概念 │ │
│ │ 性能分析工具(nsys/ncu) ⭐ 会用即可 │ │
│ │ DPU/存算一体 ⭐ 理解概念 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘各章节详情
| 章节 | 主题 | 核心场景 |
|---|---|---|
| [[01-computer-architecture-basics]] | 计算机架构基础 | 为什么 GPU 比 CPU 更适合训练任务 |
| [[02-parallel-computing-theory]] | 并行计算理论 | 阿姆达尔定律告诉你 30 天的优化极限 |
| [[03-gpu-architecture]] | GPU 架构深入 | GPU 上万个核心如何分工协作 |
| [[04-cuda-programming-model]] | CUDA 编程模型 | 第一个 .cu 文件的完整编写流程 |
| [[05-cuda-kernel-and-memory]] | CUDA 内存管理 | 百亿参数如何装进显存 |
| [[06-cuda-optimization]] | CUDA 性能优化 | 优化后从 30 天缩短到 10 天 |
| [[07-openmp-simd]] | CPU 并行编程 | OpenMP + SIMD 向量化 |
| [[08-mpi-cluster-hpc]] | HPC 集群通信 | 多卡多节点分布式训练 |
| [[09-heterogeneous-computing]] | 异构计算 | CPU + GPU 如何协同 |
| [[10-dl-training-optimization]] | 训练优化实战 | 混合精度 + DeepSpeed ZeRO |
| [[11-performance-analysis-tools]] | 性能分析工具 | 找到真正的瓶颈 |
| [[12-npu-landscape]] | NPU 全景 | 昇腾/寒武纪/TPU/苹果生态 |
| [[13-future-trends]] | 未来趋势 | DPU/光计算/量子 |
开始之前:为什么硬件编程这么难?
因为硬件编程不是学"一个语言",而是学一套系统——从 C++ 代码到 GPU 电路,中间经过了好几层抽象:
你写的 C++ 代码
↓(g++ 编译)
CPU 汇编 / 机器码 → CPU 执行
你写的 CUDA C++ 代码
↓(nvcc 编译)
分离 → Host 端用 g++ 编译 → CPU 控制
→ Device 端用 ptxas 编译 → GPU 并行执行每一层的抽象都在"欺骗"你:
- C++ 让你以为内存是无限的
- CUDA 让你以为 GPU 线程是免费的
- 分布式训练让你以为计算是线性加速的
学完这 13 章,你就能看穿这些"谎言",写出真正高效的代码。
学习状态:🟡 开始学习
完整迁入:原处理器体系结构:流水线到多核
处理器体系结构:从指令流水线到多核芯片 / Processor Architecture from Instruction Pipelines to Multicore Chips
📅 创建时间:2026-07-20 🏷️ 标签:#CPU #体系结构 #流水线 #乱序执行 #多核 📚 前置知识:[[01-parallel-foundations]] 📚 相关知识:[[/02-systems-and-performance/02-computer-architecture-and-hardware/01-computer-architecture-basics]]
1. 一条指令怎样执行
处理器执行指令通常可以抽象为:
取指 → 译码 → 读取操作数 → 执行 → 访存 → 写回如果每条指令必须完整结束后才能开始下一条,大量硬件会处于空闲。因此处理器使用流水线,让多条指令处于不同阶段。
周期 1:指令 A 取指
周期 2:指令 A 译码,指令 B 取指
周期 3:指令 A 执行,指令 B 译码,指令 C 取指流水线提高吞吐,但不会等比例降低单条指令的延迟。
2. 指令级并行
现代 CPU 会在单个核心内部寻找互不依赖的指令并行执行。
超标量
一个周期可以发射多条指令到不同执行单元,例如整数单元、浮点单元和加载存储单元。
乱序执行
a = load(slow_address); // Cache Miss
b = x + y; // 与 a 无关
c = m * n; // 与 a 无关CPU 不必等待第一条加载结束,可以先执行后两条指令,最后按程序顺序提交结果。
分支预测
遇到 if 时,CPU 会预测执行路径并提前工作。预测错误后,错误路径的流水线结果必须丢弃。
这也是规则循环通常比大量不可预测分支更容易获得高性能的原因。
3. 从提高频率转向增加核心
早期处理器主要依赖提高时钟频率,但功耗近似受到频率和电压的共同影响:
动态功耗 ≈ 电容 × 电压² × 频率频率继续增长带来严重发热和能耗问题,行业转向:
- 多核 CPU
- 更宽的 SIMD
- 专用加速器
- 大规模 GPU
这意味着软件必须显式或隐式地利用并行,才能继续获得性能增长。
4. 核心、硬件线程与逻辑处理器
物理核心
拥有独立的执行资源,可以真正与其他核心并行。
同时多线程 SMT
一个物理核心维护多个硬件线程上下文。当一个线程等待数据时,另一个线程使用部分空闲执行资源。
8 核 16 线程 ≠ 16 个完整核心SMT 的收益依赖工作负载。两个都占满相同执行单元的线程可能互相竞争。
5. CPU、GPU 与专用加速器
| 特性 | CPU | GPU | NPU/TPU |
|---|---|---|---|
| 核心数量 | 少量复杂核心 | 大量较简单执行单元 | 大量矩阵/张量单元 |
| 控制能力 | 强 | 中等 | 较弱或领域限定 |
| 单线程延迟 | 低 | 较高 | 依任务而定 |
| 吞吐能力 | 中等 | 高 | 特定算子极高 |
| 适合任务 | 操作系统、业务逻辑、复杂分支 | 图形、矩阵、规则数据并行 | AI 推理与训练 |
CPU 花费大量晶体管降低单线程延迟,GPU 把更多面积用于执行单元以提升总吞吐。
6. 并行层次
一台现代计算机中同时存在多层并行:
多节点并行
└─ 单节点多处理器/多 GPU
└─ 多核并行
└─ SMT 硬件线程
└─ SIMD 向量并行
└─ 流水线与乱序执行高性能程序通常不是只选择其中一种,而是组合多层并行。例如 MPI 跨节点、OpenMP 跨 CPU 核、SIMD 处理核心内部的数据。
7. 峰值性能不等于应用性能
理论浮点峰值可以粗略表示为:
峰值 FLOPS = 核心数 × 频率 × 每周期浮点操作数但应用还受到以下因素限制:
- 数据是否在 Cache 中
- 指令是否能够并行发射
- 分支预测是否准确
- SIMD 通道是否充分利用
- 内存带宽是否足够
- 多核之间是否频繁同步
因此硬件参数是上限,不是性能承诺。
核心总结
- 单核内部已经包含流水线、乱序执行和 SIMD 等并行机制。
- 多核是功耗墙之后性能增长的主要方向。
- CPU 追求低延迟和通用性,GPU 追求高吞吐。
- 真正的并行程序往往同时利用多个体系结构层次。
下一篇:[[03-cpu-parallelism]] <!-- migrated-deep-dive:end -->
面试速答
流水线、超标量和乱序执行有什么区别?
流水线让不同指令占据不同处理阶段;超标量允许同周期处理多条指令;乱序执行让已就绪的独立指令越过等待者,但通常按程序顺序退休以维持精确状态。
分支预测失败为什么昂贵?
错误路径上的在途工作要被清空,前端从正确地址重新填充;代价依赖流水线、预测器、工作集和具体微架构。
自测与答案
- 流水线提高延迟还是吞吐? **答案:**主要提高稳态吞吐;单条指令延迟不必降低。
- 乱序执行是否改变单线程可观察语义? **答案:**正常实现必须保持 ISA 规定的可观察结果,通常借助按序退休。
- SMT 为什么不等于两个物理核心? **答案:**硬件线程共享同一核心的许多执行和缓存资源。
下一篇:Cache、一致性与 NUMA