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

本页目录

C++ 多线程与 OpenMP ​

多核不会自动让普通串行 C++ 变快。必须把可独立工作拆开,并处理共享数据、同步、粒度与负载均衡。

1. std::thread:显式线程 ​

cpp
#include <thread>
#include <vector>

void scale(float* x, std::size_t begin, std::size_t end, float k) {
    for (std::size_t i = begin; i < end; ++i) x[i] *= k;
}

std::thread a(scale, data, 0, n / 2, 2.0f);
std::thread b(scale, data, n / 2, n, 2.0f);
a.join();
b.join();
1
2
3
4
5
6
7
8
9
10
11

join() 等待线程结束,也建立必要的同步关系。真实程序更常用线程池,避免为大量小任务频繁创建线程。

2. 数据竞争不是普通逻辑错误 ​

两个线程并发访问同一内存位置,至少一个是写,并且缺少同步,就构成 data race;在 C++ 内存模型中通常导致未定义行为。

cpp
// wrong: shared counter++ from multiple threads
// choices: mutex, atomic, thread-local partial result + reduction
1
2

互斥锁保护复合不变量;原子适合可由原子操作表达的共享状态;局部结果最后归约往往扩展性更好。原子不等于“整段算法线程安全”。

3. OpenMP:用指令描述并行区域 ​

cpp
double sum = 0.0;
#pragma omp parallel for reduction(+:sum)
for (int i = 0; i < n; ++i) {
    sum += a[i];
}
1
2
3
4
5

典型编译参数:GCC/Clang 使用 -fopenmp,MSVC 使用相应 OpenMP 选项。是否支持某个 OpenMP 版本取决于编译器与运行时。

关键概念:

  • parallel 创建线程团队;
  • for 将循环迭代分配给线程;
  • reduction 为线程建立局部值并在末尾合并;
  • private / firstprivate 控制变量副本;
  • critical、atomic、barrier 提供不同同步语义;
  • task 更适合不规则任务图,而不只是规则循环。

4. 调度策略 ​

策略适合代价
static每次迭代成本接近调度开销低,负载不均时可能闲置
dynamic迭代成本差异大负载更均衡,调度开销更高
guided开始大块、随后缩小在开销与均衡之间折中

不要看到不均衡就机械改 dynamic;先测量每项工作差异和任务粒度。

5. 常见性能陷阱 ​

  • 临界区放进热循环,线程大部分时间等待;
  • 多线程写同一 Cache line 的不同变量,产生伪共享;
  • 任务过小,调度成本高于计算;
  • 首次触碰内存与线程所在 NUMA 节点不匹配;
  • 嵌套并行导致线程过量,反而频繁切换;
  • 只比较墙钟时间一次,没有固定输入与线程数。

深入原理与工程实践 ​

前面的内容负责建立统一心智模型;下面把同一主题继续拆到执行过程、代码、性能代价与工程判断。

<!-- migrated-deep-dive:start -->

完整迁入:原 OpenMP 与 SIMD 全文 ​

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

学习状态:🟡 开始学习


完整迁入:原 CPU 并行全文 ​

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

📅 创建时间:2026-07-20 🏷️ 标签:#CPU #多线程 #SIMD #NUMA #OpenMP 📚 前置知识:[[02-processor-architecture]] 📚 相关知识:[[/02-systems-and-performance/02-computer-architecture-and-hardware/07-openmp-simd]] [[/01-cpp/06-concurrency/04-concurrency]]


1. CPU 并行的三个层次 ​

text
线程级并行:多个核心执行不同线程
数据级并行:一条 SIMD 指令处理多个元素
任务级并行:不同任务组成流水线或任务图
1
2
3

例如矩阵运算可以把行分给多个线程,每个线程内部再使用 AVX 指令一次计算多个浮点数。


2. 线程并行 ​

线程创建不是免费的 ​

创建和销毁线程涉及系统调用、栈空间和调度,因此工程中通常使用线程池。

text
任务提交 → 工作队列 → 固定数量工作线程 → 执行结果
1

线程数量并非越多越好:

  • 计算密集型任务通常接近物理核心数
  • I/O 密集型任务可以更多
  • 使用 SMT 时需要实际测量
  • 第三方数学库可能已经创建线程,避免嵌套过度并行
OpenMP 示例 ​
cpp
#pragma omp parallel for
for (int i = 0; i < n; ++i) {
    output[i] = compute(input[i]);
}
1
2
3
4

OpenMP 适合逐步并行化规则循环,但仍需检查数据竞争和任务粒度。


3. SIMD 向量化 ​

标量加法一次处理一个元素:

text
a0+b0 → c0
1

256 位 AVX 指令可以一次处理 8 个 FP32:

text
[a0..a7] + [b0..b7] → [c0..c7]
1

编译器自动向量化偏爱:

  • 连续内存访问
  • 简单、固定次数的循环
  • 迭代之间没有依赖
  • 容易证明指针不重叠
  • 分支较少
不利示例 ​
cpp
for (int i = 1; i < n; ++i) {
    a[i] = a[i - 1] * 0.5f; // 当前迭代依赖前一次结果
}
1
2
3

这是循环携带依赖,不能直接并行执行各次迭代。


4. 数据竞争与同步 ​

cpp
counter++; // 读取、加一、写回,并非不可分割操作
1

多个线程同时修改会导致结果丢失。常用保护方式:

  • Mutex:保护复杂临界区
  • Atomic:保护简单原子状态
  • Reduction:每个线程局部计算,最后合并
  • 消息传递:避免共享可变状态

并行归约通常优于每次循环都锁住全局变量。


5. 伪共享 ​

即使线程修改不同变量,只要变量位于同一个 Cache Line,也可能互相使缓存失效。

cpp
struct Counters {
    long a; // 线程 A 修改
    long b; // 线程 B 修改,但可能与 a 在同一 Cache Line
};
1
2
3
4

常见处理方法:

  • 对高频写入的线程局部变量进行 Cache Line 对齐
  • 每线程维护局部统计值
  • 降低共享写入频率

伪共享不会造成结果错误,但会造成严重性能下降。


6. NUMA:内存也有远近 ​

多路服务器中,每颗 CPU 插槽通常连接自己的本地内存。

text
CPU 0 ─ 本地内存 0
  │
互联总线
  │
CPU 1 ─ 本地内存 1
1
2
3
4
5

CPU 0 访问内存 1 的延迟更高、带宽可能更低,这就是非统一内存访问 NUMA。

NUMA 优化原则 ​
  • First Touch:由实际使用数据的线程首次初始化数据
  • 线程绑定:避免线程在不同 NUMA 节点之间迁移
  • 数据分区:让线程主要访问本地内存
  • 测量远程访问比例,而不是凭感觉绑定

7. CPU 并行适用场景 ​

CPU 更适合:

  • 分支复杂、任务差异大的计算
  • 数据规模较小、要求低延迟的计算
  • 图遍历、搜索、编译、数据库执行
  • 操作系统和 I/O 协调
  • GPU kernel 前后的数据准备与控制

GPU 更适合并不意味着 CPU 不重要。多数异构应用都依赖 CPU 组织整个执行流程。


章节检查清单 ​

  • 循环迭代是否相互独立?
  • 任务粒度是否显著大于调度成本?
  • 是否存在共享写入和伪共享?
  • 编译器是否成功向量化?
  • 线程和数据是否跨越 NUMA 节点?
  • 数学库是否已经在内部并行?

下一篇:[[04-memory-hierarchy]] <!-- migrated-deep-dive:end -->

面试速答 ​

OpenMP 是处理器吗? 不是。它是一套共享内存并行编程接口,由编译器指令、运行时库和环境变量共同实现。

什么时候用 std::thread,什么时候用 OpenMP? 需要长期线程、精细生命周期和自定义同步时偏向 C++ 并发设施;规则循环和科学计算快速并行化时 OpenMP 更直接。

单核能运行多线程吗? 能并发运行,但 CPU 密集线程通常通过时间片交替,不会获得真正多核并行吞吐。

自测 ​

  1. volatile 能否修复 data race?
  2. 为什么 reduction 常比每次加锁好?
  3. 线程数为何不应盲目超过逻辑 CPU 数量?

答案:不能;它把频繁共享更新变成局部累积和少量合并;过量线程会增加切换、竞争和缓存压力,除非等待型工作另有理由。

最后更新于:

Pager
上一篇7. SIMD 与编译器向量化
下一篇9. CUDA 平台与编程模型

持续记录,持续成长

Copyright © Tidenflow