并行计算概述:从单核困境到多核爆发
说起并行计算,我印象最深的是刚入行那会儿。当时做图像处理,一张 4K 图片的滤波运算要跑好几秒。组长走过来看了一眼,说:“你知不知道 CPU 还有好几个核闲着?” 从那以后,我开始认真琢磨并行这件事。
什么是并行计算?
说白了,并行计算就是同时用多个计算资源解决问题。你想想看,一个任务拆成十份,十个人同时干,理论上快十倍。但现实没这么理想——拆任务、分数据、等结果,这些开销都会吃掉一部分加速。
并行计算的核心思路很简单:
- 任务分解——把一个大任务切成独立的小块
- 数据分配——每个计算单元拿到自己那部分数据
- 结果聚合——各单元算完后拼回最终结果
我在项目中见过不少新手,上来就把循环改成 OpenMP,结果反而更慢了。为什么?因为任务太小,拆分的开销比计算本身还大。
| 对比维度 | 串行计算 | 并行计算 |
|---|---|---|
| 执行方式 | 一条流水线 | 多条流水线 |
| 资源利用率 | 单核单线程 | 多核多节点 |
| 加速瓶颈 | 主频极限 | 通信与同步开销 |
| 编程难度 | 低 | 较高 |
发展历程:几个关键节点
并行计算不是新鲜事。我记得最早接触是在研究生课上,老师讲到 70 年代的向量机。那时候的并行思想就已经有了雏形。
萌芽期(1970s-1980s)
超级计算机登场。Cray-1 用向量处理的方式实现了最早的指令级并行。当时的程序员得手写向量代码,调优全靠汇编。
成熟期(1990s-2000s)
MPI 和 OpenMP 两个标准出现,把并行编程规范化了。我自己第一次写 MPI 程序时,光是进程间通信就调试了两天。后来才发现是个死锁,两个进程都在等对方发消息——嗯,这就是经典的“互相等待”陷阱。
爆发期(2005 至今)
多核 CPU 普及,GPU 异军突起。英特尔搞出了 72 核的 Xeon Phi,NVIDIA 的 CUDA 把 GPU 变成了通用计算利器。我在 2015 年用 CUDA 写过分子动力学模拟,那种从 CPU 几天缩短到 GPU 几小时的体验,确实震撼。
一点个人体会:学并行计算别追求一次性掌握所有架构。先从共享内存(OpenMP)入手,理解线程并行;再接触分布式(MPI),搞懂进程通信;最后再碰异构(CUDA)。这个顺序我都走过,比较踏实。
主流并行架构
现在常见的并行架构有三种,每种都有自己的脾气。
共享内存架构
多个处理器共享同一块物理内存。优点是不用操心数据在哪,缺点是你得处理好线程打架的问题(也就是竞态条件)。
编程模型主要是 OpenMP 和 POSIX Threads。我习惯用 OpenMP,因为用编译指导语句就能把串行代码并行化,侵入性小。
// OpenMP 示例:并行累加
#include <omp.h>
double sum = 0.0;
#pragma omp parallel for reduction(+:sum)
for (int i = 0; i < N; i++) {
sum += data[i];
}
我曾经在一个项目中犯过错:多个线程同时写同一个全局变量,没加锁也没用 reduction。结果每次跑出来的结果都不一样。后来花了一整天排查——其实就是一行代码的问题。
分布式内存架构
每个节点有自己的内存,节点间通过网络通信。典型的代表就是集群系统。
MPI 是这里的老大哥。你写程序时得显式地发消息、收消息。刚开始会觉得麻烦,但习惯了之后,它对海量数据的处理能力确实强。
// MPI 示例:发送和接收
if (rank == 0) {
MPI_Send(data, count, MPI_DOUBLE, 1, tag, MPI_COMM_WORLD);
} else if (rank == 1) {
MPI_Recv(data, count, MPI_DOUBLE, 0, tag, MPI_COMM_WORLD, MPI_STATUS_IGNORE);
}
异构架构
CPU + GPU/FPGA 的组合。CPU 负责逻辑控制,加速器负责密集计算。CUDA 和 HIP 是主流选择。
我记得第一次用 CUDA 时,被它的内存层次搞晕了——全局内存、共享内存、寄存器一堆概念。但其实理解了“数据是怎么搬的”,后面就顺了。
// CUDA 内核示例:向量加法
__global__ void vecAdd(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];
}
}
| 架构 | 通信方式 | 典型平台 | 编程模型 | 适用场景 |
|---|---|---|---|---|
| 共享内存 | 内存总线 | 多核 CPU | OpenMP, Pthreads | 中规模数值计算 |
| 分布式内存 | 网络通信 | 集群 | MPI | 大规模 HPC |
| 异构 | PCIe/NVLink | GPU/FPGA | CUDA, OpenCL | 深度学习、科学计算 |
应用场景
并行计算现在覆盖面很广,说几个我接触过的典型场景:
- 科学计算:天气预报、分子模拟、流体力学。这类任务计算量大,但数据依赖少,并行效果好。
- 机器学习:训练大模型。一张 GPU 显存放不下时,就得用多卡甚至多机并行。我做过的 BERT 训练,光数据并行和模型并行的切分策略就调了两周。
- 图像与视频处理:渲染、编码、实时滤镜。每个像素独立处理,天生适合 GPU。
- 数据库与大数据:MapReduce、Spark 这些框架底层就是并行计算。你在 Map 阶段把数据分片,Reduce 阶段汇总结果。
本章知识体系
下面这张图我画了三层:先讲为什么需要并行(驱动力),再讲怎么实现(架构与编程),最后落脚到典型场景。你对着这个图梳理思路,会比较清楚。
简单总结一下:并行计算不是什么高深莫测的东西。它就是“人多力量大”在计算机里的体现。选对架构、分好任务、管好通信,性能自然就上来了。我自己走过不少弯路,后面几章会把那些实实在在的调优经验都分享出来。