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

外观

本页目录

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
下一篇← 系统与高性能 / Systems & Performance

持续记录,持续成长

Copyright © Tidenflow