先记住一句话
CPU 性能往往由 cache miss、分支、向量化和 NUMA 决定,而不是源码中乘加次数;先让数据靠近执行它的 core。
1. CPU 与 GPU 的取向
| CPU | GPU |
|---|---|
| 少量复杂 cores,大 cache,强 branch/speculation | 大量 throughput cores,以并发隐藏 latency |
| 适合 irregular control、OS、serial work | 适合规则 dense data parallel work |
| 单线程 latency 强 | 大批量 aggregate throughput 强 |
HPC 系统通常是 heterogeneous:CPU 组织任务,GPU 执行高并行 kernels,NIC/NVLink 搬数据。
2. Cache line 与 locality
CPU 不是按单个 float 从 DRAM 取数据,而是按 cache line。连续遍历数组有 spatial locality,重复使用有 temporal locality;随机 pointer chasing 会让每次只用 cache line 少数字节。AoS 与 SoA layout 的优劣取决于实际访问字段。
3. SIMD/vectorization
SIMD 一条指令处理多个 lanes。编译器需要连续/可推断的 memory access、无 loop-carried dependency、明确 aliasing 与适合的 alignment。查看 vectorization report 和 assembly 比猜测更可靠;tail elements 需 mask/scalar cleanup。
4. Branch 与 pipeline
现代 CPU 用 out-of-order execution、branch prediction 和 speculation 隐藏 latency。不可预测 branch 会 flush pipeline;但把所有 branch 改成 branchless 也可能增加无用 work。应基于 branch-miss counter 判断。
5. 多线程与 false sharing
线程写不同变量,但变量落在同一 cache line 时,coherence 仍让 line 在 cores 间来回失效,称 false sharing。per-thread counters 要 padding/alignment 或局部累积后 reduction。锁、atomic 和 oversubscription 也会把 parallel code 串行化。
6. NUMA
多 socket 系统中,memory physically attached to a NUMA node。本地内存 latency/bandwidth 优于跨 socket。first-touch policy、CPU affinity、GPU/NIC PCIe locality 都重要:
process/thread pinning
↕ local DRAM
CPU socket ─ PCIe root ─ GPU / NICworker 被调度到另一 socket,却读原 node 内存或驱动远端 GPU,会产生隐蔽带宽损失。
7. OpenMP、MPI 与 task runtime
- OpenMP/pthreads:共享内存 node 内线程并行;
- MPI:进程 + message passing,可跨 nodes,显式 ownership;
- CUDA/NCCL:GPU compute 与 collective communication;
- task runtimes:用 DAG 表达 dependencies,调度 heterogeneous resources。
MPI rank、CPU cores、GPU 与 NIC 的 mapping 是性能配置的一部分。
8. GPU pipeline 中常见 CPU 瓶颈
- Python/data loader/tokenizer 赶不上 device;
- pageable memory 使 H2D copy 无法有效异步;
- 大量 tiny kernels,host enqueue 时间大于 GPU work;
- 线程争锁、allocator、logging 或 RPC serialization;
- 错误 NUMA affinity 导致远端 memory/NIC/GPU access。
9. 测什么
wall-clock 外还看 IPC、cycles、cache/LLC misses、branch misses、memory bandwidth、context switches、NUMA remote accesses 与 CPU utilization per core。Linux perf、VTune、Nsight Systems 与 eBPF 各覆盖不同层。
10. 四个 CPU locality 手算
例 1:cache-line 利用率
64-byte cache line 装 16 个 FP32。若 stride=16、每条 line 只读 1 个数,有效利用率=4/64=6.25%;连续读取则可接近 100%。
例 2:SIMD lane 数
256-bit vector 一次容纳 8 个 FP32。处理 1024 elements 理论需 128 vector iterations,而 scalar 需 1024;实际 speedup 还受 memory、tail 与 instruction mix 限制。
例 3:false sharing
两个 threads 各更新同一 64-byte line 上不同 8-byte counters。逻辑上无 data race,但每次写都可能让 cache line 在 cores 间 ping-pong;把 counters padding 到不同 lines 可消除 coherence 流量。
例 4:NUMA 带宽
处理 40 GB 数据,local bandwidth=100 GB/s,纯读下限 0.4 s;若错误放到 remote node 只有 50 GB/s,下限 0.8 s,kernel 算术完全没变却慢 2×。
“GPU kernel 很快,所以 CPU 无所谓”正好相反:kernel 越短,Python/launch/scheduling/serialization 越可能成为 Amdahl serial fraction。
自测
1. false sharing 为什么没有共享变量也会慢?
cache coherence 以 cache line 为单位;不同变量若在同一 line,多个 core 写入仍互相使缓存失效。
2. NUMA first-touch 表示什么?
内存页通常分配到首次写入它的 CPU node,因此初始化线程位置会影响后续 locality。
3. SIMD 和 GPU SIMT 相同吗?
都利用 lane parallelism,但 CPU SIMD 是向量指令接口;GPU SIMT 让标量 threads 以 warp 组调度,控制流与内存语义不同。
继续学习
CPU 侧应结合具体架构的 optimization manual、编译器 vectorization report 与硬件 counters;本系列下一篇转入 CUDA Programming Guide定义的 GPU execution model。