Skip to content
Gains Summary
Main Navigation 首页 / Home
C++ 编程 / C++ Programming
系统与高性能 / Systems & Performance
Web 开发 / Web Development
人工智能 / Artificial Intelligence
工业软件 / Industrial Software
其他内容 / Other Topics
C++ 编程 / C++系统与性能 / SystemsWeb 开发 / Web人工智能 / AI工业软件 / Industrial

外观

Sidebar Navigation

← 系统与高性能 / Systems & Performance

计算系统 / Computing Systems

1. 计算系统:计算机如何执行与加速程序

2. 从 C++ 源码到 CPU 执行

3. CPU 流水线、乱序执行与分支预测

4. Cache、一致性、伪共享与 NUMA

5. GPU、SM、Warp 与显存

6. 计算执行模型:程序怎样映射到机器

7. SIMD 与编译器向量化

8. C++ 多线程与 OpenMP

9. CUDA 平台与编程模型

10. CUDA Kernel、内存与性能

11. CPU-GPU 异构流水线

12. MPI 与分布式并行

13. 并行算法模式

14. 性能模型与工具

15. 递进学习项目:从单线程到集群

历史完整正文 / Original Deep Dives

1. 历史完整正文:统一前文章逐篇保留

原体系结构与硬件 / Original Architecture

1. 硬件编程与高性能计算:一张可走通的学习地图 / A Practical Learning Map for Hardware Programming and HPC

2. 计算机体系结构:CPU、内存与 GPU / Computer Architecture: CPUs, Memory, and GPUs

3. 计算机架构基础——为什么 GPU 比 CPU 更快 / Computer Architecture Fundamentals: Why GPUs Outperform CPUs

4. 并行计算理论——30 天训练能优化到多快? / Parallel Computing Theory and the Limits of Training Acceleration

5. GPU 架构深入——上万个核心如何分工协作 / GPU Architecture and Massive Parallel Execution

6. CUDA 编程模型——把矩阵乘法映射到 GPU / The CUDA Programming Model for Mapping Matrix Multiplication to GPUs

7. CUDA 内存管理——百亿参数如何装进显存 / CUDA Memory Management for Large Models

8. CUDA 性能优化——从 30 天缩短到 10 天 / CUDA Performance Optimization

9. CPU 并行编程——OpenMP 与 SIMD 向量化 / CPU Parallel Programming with OpenMP and SIMD

10. HPC 集群与 MPI——多节点分布式训练 / HPC Clusters and MPI for Distributed Training

11. 异构计算——CPU 与 GPU 如何协同工作 / Heterogeneous Computing with CPUs and GPUs

12. 深度学习训练优化实战——从 30 天到 3 天 / Deep Learning Training Optimization from Thirty Days to Three

13. 性能分析工具——找到真正的瓶颈 / Performance Analysis Tools for Finding Real Bottlenecks

14. NPU 全景——昇腾/寒武纪/TPU/苹果生态 / The NPU Landscape: Ascend, Cambricon, TPU, and Apple

15. 未来趋势——2030 年的计算机会是什么形态 / Future Computing Trends Toward 2030

16. 硬件与高性能计算:从“程序为什么慢”开始 / Hardware and HPC Starting from Why Programs Are Slow

原并行计算 / Original Parallel Computing

1. 并行计算:从 SIMD 到 MPI / Parallel Computing from SIMD to MPI

2. 并行计算全景:从晶体管、CPU、GPU 到计算集群 / Parallel Computing from Transistors, CPUs, and GPUs to Clusters

3. 并行计算基础:任务分解、加速比与可扩展性 / Parallel Computing Fundamentals: Decomposition, Speedup, and Scalability

4. 处理器体系结构:从指令流水线到多核芯片 / Processor Architecture from Instruction Pipelines to Multicore Chips

5. CPU 并行:多线程、SIMD、Cache 一致性与 NUMA / CPU Parallelism with Threads, SIMD, Cache Coherence, and NUMA

6. 内存层次:Cache、带宽、局部性与一致性 / Memory Hierarchies, Bandwidth, Locality, and Coherence

7. GPU 体系结构:SIMT、Warp、SM 与吞吐优先设计 / GPU Architecture with SIMT, Warps, and Streaming Multiprocessors

8. CUDA 编程模型:Thread、Block、Grid 与内存协作 / CUDA Threads, Blocks, Grids, and Cooperative Memory Access

9. 并行算法模式:Map、Reduce、Scan、Stencil 与任务图 / Parallel Patterns: Map, Reduce, Scan, Stencil, and Task Graphs

10. 异构计算:CPU、GPU、NPU 如何协同工作 / Heterogeneous Computing with CPUs, GPUs, and NPUs

11. 分布式并行:MPI、集合通信、RDMA 与多机多卡 / Distributed Parallelism with MPI, Collective Communication, and RDMA

12. 性能工程:测量、Roofline、瓶颈定位与优化闭环 / Performance Engineering with Measurement, Roofline, and Bottleneck Analysis

13. 并行计算实战:AI、CAE、图像与科学计算 / Parallel Computing for AI, CAE, Imaging, and Scientific Computing

14. 并行计算实践路线:从单核优化到多机多卡 / A Parallel Computing Project Path from Single-Core to Multi-Node GPUs

本页目录

CPU 并行编程——OpenMP 与 SIMD 向量化 / CPU Parallel Programming with OpenMP and SIMD ​

📅 创建时间:2026-06-02 🏷️ 标签:#CPU #OpenMP #SIMD #AVX512 #并行化 #向量化 📚 前置知识:[[01-computer-architecture-basics]](CPU 架构) 📚 相关知识:[[04-cuda-programming-model]](CUDA 编程,对比学习) [[09-heterogeneous-computing]](异构计算)


先抓住直觉 ​

CPU 也有两种“同时做”:OpenMP 把不同任务交给多个 CPU 核心,SIMD 让一个核心用一条指令处理多个数据。前者像增加工人,后者像让每个工人一次端多盘菜。

  • 必须理解:线程级并行与数据级并行的区别;数据竞争;并行归约。
  • 用到再查:pragma 子句、AVX Intrinsics 和编译器参数。
  • 读完能回答:为什么 CPU 数据预处理会让 GPU 空等?OpenMP 和 SIMD 能否叠加?

场景:GPU 在训练,CPU 也不能闲着 ​

┌─────────────────────────────────────────────────────────────┐
│                                                             │
│  深度学习训练不只是 GPU 的工作:                           │
│                                                             │
│  GPU 负责:                                                 │
│  - 前向传播(矩阵乘法)                                    │
│  - 反向传播(梯度计算)                                    │
│                                                             │
│  CPU 负责:                                                 │
│  - 数据加载:从 SSD/HDD 读取训练数据                       │
│  - 数据预处理:数据增强、归一化、Tokenization              │
│  - 分布式协调:多卡梯度同步的控制逻辑                      │
│  - 日志记录、Checkpoint 保存                               │
│                                                             │
│  如果 CPU 处理太慢,会拖慢整个训练流程!                    │
│  本章学习如何用 OpenMP + SIMD 加速 CPU 端计算。            │
│                                                             │
└─────────────────────────────────────────────────────────────┘
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

第1节:OpenMP——几行代码实现多核并行 ​

什么是 OpenMP? ​

┌─────────────────────────────────────────────────────────────┐
│                    OpenMP 是什么?                          │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  OpenMP = Open Multi-Processing                           │
│  一套编译器指令(#pragma omp),告诉编译器如何并行化       │
│                                                             │
│  对比 CUDA:                                               │
│  CUDA:需要学习新的编程模型(kernel、grid、block)         │
│  OpenMP:在现有 C++ 代码上加几行 #pragma                  │
│                                                             │
│  本质:                                                   │
│  自动把 for 循环分配到多个 CPU 核心                         │
│  编译器生成线程创建/管理代码,程序员只需加指令              │
│                                                             │
└─────────────────────────────────────────────────────────────┘
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

最简单的例子 ​

cpp
// C++ 单线程版本
#include <vector>

float sum = 0.0f;
for (int i = 0; i < 1000000; i++) {
    sum += data[i];
}
1
2
3
4
5
6
7
cpp
// OpenMP 并行版本(只加了一行)
#include <vector>
#include <omp.h>  // OpenMP 头文件

float sum = 0.0f;

#pragma omp parallel for
for (int i = 0; i < 1000000; i++) {
    sum += data[i];  // ⚠️ 问题:多线程同时写 sum,需要归约
}
1
2
3
4
5
6
7
8
9
10

并行归约(Reduction) ​

cpp
// ❌ 错误:多线程同时写 sum,竞态条件(race condition)
#pragma omp parallel for
for (int i = 0; i < 1000000; i++) {
    sum += data[i];  // 多个线程同时读-改-写同一个变量
}

// ✅ 正确:使用 reduction 子句
#pragma omp parallel for reduction(+:sum)
for (int i = 0; i < 1000000; i++) {
    sum += data[i];
}

// reduction(+:sum) 的含义:
// 1. 每个线程有自己的 sum 副本(初始化为 0)
// 2. 每个线程累加到自己的 sum
// 3. 最后把所有线程的 sum 加起来
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

常用 OpenMP 子句 ​

┌─────────────────────────────────────────────────────────────┐
│                    OpenMP 常用子句                           │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  #pragma omp parallel for [子句...]                        │
│                                                             │
│  常用子句:                                                 │
│  - reduction(op:list)    归约操作                          │
│  - schedule(kind, chunk)  循环调度策略                      │
│  - num_threads(n)         指定线程数                        │
│  - collapse(n)            把 n 层循环合并为一维并行         │
│  - nowait                 去掉隐式 barrier(同步)         │
│                                                             │
│  schedule 子句:                                           │
│  - static: 循环均匀分块(编译时确定)                     │
│  - dynamic: 动态分块(运行时按需分配)                     │
│  - guided: 动态递减块大小                                 │
│  - runtime: 由 OMP_SCHEDULE 环境变量决定                  │
│                                                             │
└─────────────────────────────────────────────────────────────┘
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

矩阵乘法的 OpenMP 版本 ​

cpp
// OpenMP 矩阵乘法
void matrix_mul_openmp(float* C, float* A, float* B,
                       int M, int N, int K) {
    #pragma omp parallel for collapse(2)
    for (int i = 0; i < M; i++) {
        for (int j = 0; j < N; j++) {
            float sum = 0.0f;
            for (int k = 0; k < K; k++) {
                sum += A[i * K + k] * B[k * N + j];
            }
            C[i * N + j] = sum;
        }
    }
}

// collapse(2) 把两层循环合并为一个 2D 循环
// 原来:M 个外层迭代
// 合并后:M × N 个迭代,并行度更高
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

线程数控制 ​

cpp
// 方式1:运行时函数
omp_set_num_threads(16);  // 设置后续 parallel 的线程数

// 方式2:环境变量
// export OMP_NUM_THREADS=16

// 方式3:子句
#pragma omp parallel for num_threads(8)

// 方式4:系统自动(推荐)
// 不设置,让 OpenMP 自动选择 = CPU 核心数
1
2
3
4
5
6
7
8
9
10
11

第2节:SIMD 向量化——一条指令处理多个数据 ​

SIMD 原理 ​

┌─────────────────────────────────────────────────────────────┐
│                    SIMD:单指令多数据                        │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  标量计算(一次处理一个数):                              │
│  CPU: [ALU] → result                                       │
│  执行 4 次:4 × 4 周期 = 16 周期                         │
│                                                             │
│  SIMD 计算(一次处理 8 个 float):                        │
│  CPU: [ALU][ALU][ALU][ALU][ALU][ALU][ALU][ALU]          │
│  → 一次执行 8 个结果,1 个周期完成                        │
│                                                             │
│  AVX-512 = 512 位 = 16 × 32-bit float = 16 个 float     │
│  SSE = 128 位 = 4 × 32-bit float                        │
│  NEON = ARM 的 SIMD = 128 位                              │
│                                                             │
└─────────────────────────────────────────────────────────────┘
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

手动 SIMD(Intrinsics) ​

cpp
// 使用 AVX-512 intrinsics(Intel/AMD x86)
#include <immintrin.h>

void vector_add_avx512(float* a, float* b, float* c, int N) {
    // 一次处理 16 个 float(16 × 32-bit = 512-bit)
    for (int i = 0; i < N; i += 16) {
        // __m512 = 512-bit 寄存器(16 个 float)
        __m512 va = _mm512_loadu_ps(&a[i]);   // 加载 16 个 float
        __m512 vb = _mm512_loadu_ps(&b[i]);
        __m512 vc = _mm512_add_ps(va, vb);     // 16 个加法并行执行
        _mm512_storeu_ps(&c[i], vc);            // 存储结果
    }
}

// 常用 intrinsics:
// _mm512_loadu_ps / _mm512_storeu_ps  加载/存储
// _mm512_add_ps / _mm512_sub_ps        加减
// _mm512_mul_ps / _mm512_div_ps        乘除
// _mm512_fmadd_ps                       融合乘加(a*b+c)
// _mm512_set1_ps                         创建常数向量
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

融合乘加(FMA)——矩阵乘法的关键 ​

cpp
// 普通计算:2 次操作
c[i] = a[i] * b[i] + c[i];
// 编译后可能需要 2 条指令

// FMA:1 次操作(乘加融合)
// c[i] = a[i] * b[i] + c[i]
__m512 va = _mm512_loadu_ps(&a[i]);
__m512 vb = _mm512_loadu_ps(&b[i]);
__m512 vc = _mm512_loadu_ps(&c[i]);
vc = _mm512_fmadd_ps(va, vb, vc);  // vc = va * vb + vc
_mm512_storeu_ps(&c[i], vc);

// FMA 优点:
// 1. 减少指令数(性能提升)
// 2. 减少舍入误差(一次运算 vs 两次)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

OpenMP 自动向量化 ​

cpp
// 编译器自动 SIMD 化(GCC/Clang 需要 -O3 -march=native)
#pragma omp parallel for simd
for (int i = 0; i < N; i++) {
    c[i] = a[i] + b[i];
}

// simd 子句告诉编译器:循环可以安全地向量化
// 编译器会自动选择 SIMD 宽度和指令
1
2
3
4
5
6
7
8

第3节:OpenMP + SIMD 组合使用 ​

完整的数据预处理流水线 ​

cpp
// 场景:图像数据增强(CPU 端)
struct ImageBatch {
    float* data;      // [batch][channel][height][width]
    int batch_size;
    int channels;
    int height;
    int width;
};

// 数据归一化:减去均值,除以标准差
void normalize_batch(float* data, int N,
                    const float* mean, const float* std,
                    float* output) {
    #pragma omp parallel for schedule(static)
    for (int i = 0; i < N; i++) {
        #pragma omp simd
        for (int c = 0; c < 3; c++) {
            output[i * 3 + c] =
                (data[i * 3 + c] - mean[c]) / std[c];
        }
    }
}

// 关键:omp parallel for + omp simd
// - parallel for:多核并行(16 核 = 16 倍)
// - simd:单核内向量并行(16 float/次 = 16 倍)
// - 总计:16 × 16 = 256 倍加速!
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27

实际性能数据 ​

┌─────────────────────────────────────────────────────────────┐
│                    性能测试对比(Intel Xeon 8380)            │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  测试:1000 张 224×224×3 图像归一化                        │
│                                                             │
│  单线程(无 SIMD)      5000 ms                             │
│  单线程(AVX-512)      600 ms   8x 加速                  │
│  16 核(无 SIMD)       320 ms   16x 加速                 │
│  16 核 + AVX-512        40 ms   125x 加速!               │
│                                                             │
│  OpenMP(多核)+ SIMD(向量化)= 双重加速                  │
│                                                             │
└─────────────────────────────────────────────────────────────┘
1
2
3
4
5
6
7
8
9
10
11
12
13
14

第4节:与 CUDA 的对比 ​

┌─────────────────────────────────────────────────────────────┐
│                    OpenMP vs CUDA                           │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  OpenMP:CPU 多核并行                                      │
│  - 核心数:8-64 核                                        │
│  - 内存:共享内存(无需拷贝)                               │
│  - 编程:加 #pragma 即可                                   │
│  - 适用:数据预处理、IO 密集任务                           │
│                                                             │
│  CUDA:GPU 大规模并行                                      │
│  - 核心数:上千核                                          │
│  - 内存:独立显存(需要拷贝)                               │
│  - 编程:学习新的编程模型                                   │
│  - 适用:计算密集任务(矩阵乘法、卷积)                    │
│                                                             │
│  组合使用:                                               │
│  CPU:数据加载 + 预处理(OpenMP+SIMD 加速)                │
│  GPU:模型训练(CUDA 加速)                                │
│  两者通过 PCIe 传输数据                                     │
│                                                             │
└─────────────────────────────────────────────────────────────┘
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

"AI 可查 vs 必须理解"清单 ​

AI 可查:
✅ AVX-512 intrinsics 的具体函数(_mm512_loadu_ps 等)
✅ OpenMP schedule 的具体参数选择
✅ OpenMP 和 SIMD 组合的最佳实践参数

必须理解:
🔴 OpenMP #pragma omp parallel for 的工作原理(自动创建线程池)
🔴 reduction 子句:解决多线程写同一变量的竞态条件
🔴 SIMD = 单指令多数据 = 一次处理多个数据
🔴 AVX-512 = 512 位 = 16 个 float 并行
🔴 OpenMP(多核)+ SIMD(向量化)= 双重加速叠加
1
2
3
4
5
6
7
8
9
10
11

学习状态:🟡 开始学习

最后更新于:

Pager
上一篇8. CUDA 性能优化——从 30 天缩短到 10 天 / CUDA Performance Optimization
下一篇10. HPC 集群与 MPI——多节点分布式训练 / HPC Clusters and MPI for Distributed Training

持续记录,持续成长

Copyright © Tidenflow