1. 课程导入与多核并行科学计算概览
科学计算的核心矛盾从未改变:我们希望在可接受的时间内求解更大规模、更高精度的问题。过去四十年,这一目标主要靠处理器主频提升来实现,程序无需改写就能自动变快。但当主频在 2005 年前后逼近功耗与散热墙,单核性能增长显著放缓,硬件厂商转向在单块芯片上集成多个核心。这意味着性能红利不再"免费":程序必须显式地划分任务、协调数据、管理同步,才能用上多出来的计算资源。本课程讨论的,正是如何为多核平台设计高效、正确、可扩展的科学计算算法。
学习目标
完成本课程后,你应当具备三方面能力。第一是分析能力:面对一个数值问题,能判断其并行性来自哪里——是数据可分(如网格逐点更新)、任务可分(如多个独立参数扫描),还是两者兼有;能估算并行加速比上界,识别串行瓶颈与通信开销。第二是实现能力:能用 OpenMP 共享内存模型、MPI 消息传递模型以及二者的混合模式,写出正确的并行代码,并处理数据竞争、伪共享、负载不均等典型问题。第三是评估能力:会用强扩展与弱扩展两把尺子衡量程序,能读懂性能剖析结果,定位是计算、访存还是同步拖了后腿。
多核处理器架构演进
理解硬件是设计算法的前提。现代多核处理器有几个关键特征值得记住。其一是缓存层次与非一致访存:每个核心有私有 L1/L2,多个核心共享 L3,而多路服务器上访问本地内存与远端内存的延迟可相差近一倍(NUMA 效应)。其二是SIMD 向量单元:一个核心每周期可对 4 个双精度数或 8 个单精度数做同一条运算,这要求数据在内存中连续且循环可向量化。其三是超线程与多核并存:逻辑核数多于物理核数,对访存密集的代码有一定掩盖延迟的作用,但对计算密集代码提升有限。其四是内存带宽成为共享资源:核心越多,每个核心分到的带宽越少,"算得快但喂不饱"是常见困境。
这些特征直接决定了算法设计取向。例如,一个在单核上表现优异的稠密矩阵乘法,若不做分块,多核并行后会迅速撞上带宽墙;而一个逐点更新的模板计算(stencil),若不考虑缓存行对齐,相邻线程写同一缓存行的不同元素会引发伪共享,性能可能不升反降。
应用场景与挑战
并行科学计算支撑着大量真实工作负载。计算流体力学用有限体积法在亿级网格上求解 Navier–Stokes 方程,每个时间步都要遍历全部单元;分子动力学对数十万原子做短程力计算,天然适合区域分解;计算化学中的稠密对角化与矩阵乘法是 BLAS 的主战场;地震成像、天气预报、结构有限元、机器学习中的大规模线性代数,也都依赖多核与集群。它们的共同点是:单次迭代计算量大、数据局部性强、存在明确的规则或不规则通信模式。
挑战同样鲜明。第一是正确性:并行程序的错误往往是非确定性的,一次运行通过不代表正确,浮点归约顺序改变也会带来结果差异,需要区分"可接受的数值差异"与"真正的逻辑错误"。第二是可扩展性:Amdahl 定律告诉我们,若程序有 5% 的串行部分,即使无限多核,加速比也不超过 20 倍。第三是数据局部性:并行化后每个核心的数据工作集变小,但总带宽不变,分块与数据布局往往比线程数更影响性能。第四是负载均衡:自适应网格、粒子模拟、稀疏矩阵的非零分布都不均匀,静态划分常导致部分核心空转。
一个可动手的起点示例
为了让概念落地,看一个最简单的例子:用梯形法计算 ∫₀¹ 4/(1+x²)dx,其真值为 π。串行版本把区间分成 N 份累加。并行时,最直接的做法是让每个线程负责一段连续子区间,各自求部分和,最后归约。下面给出 OpenMP 版本的核心片段(示例参数 N=10⁸,仅作教学演示,实际数值随机器与编译器而异):
double f(double x){ return 4.0/(1.0+x*x); }
double trap_parallel(long N){
double h = 1.0 / N, sum = 0.0;
#pragma omp parallel for reduction(+:sum) schedule(static)
for (long i = 0; i < N; ++i) {
double x = (i + 0.5) * h; // 中点法,避免端点重复计数
sum += f(x);
}
return sum * h;
}
这段代码值得注意的细节有三处。reduction(+:sum) 让每个线程在私有副本上累加,结束时再合并,避免了写共享变量的数据竞争;但归约顺序不确定,因此结果与串行版本可能有末位差异,这属于正常现象。schedule(static) 把迭代块静态分给各线程,适合每步计算量均匀的情形;若函数求值代价差异大,应改用 dynamic 或 guided。中点法取 (i+0.5)h 而非端点,既保证精度又让每个 i 的计算完全独立。运行后可用 OMP_NUM_THREADS=4 与单线程对比耗时,计算加速比 S=T₁/T₄,并观察它是否接近 4——若明显偏低,就要怀疑归约开销、内存带宽或线程创建成本。
课程知识地图与实战项目
本课程按"基础—模型—算法—性能—实战"推进。前半程建立并行性分析、加速比与扩展性度量、访存层次与向量化的共同语言;中段分别深入 OpenMP 共享内存编程(线程创建、同步原语、调度策略、伪共享规避)与 MPI 分布式编程(点对点、集合通信、区域分解、通信与计算重叠);随后进入数值算法层面,讨论稠密矩阵乘法分块、稀疏矩阵向量乘的负载均衡、迭代求解器的并行预条件、快速傅里叶变换的并行分解;最后落到性能工程与综合实战。
贯穿课程的实战项目是:并行求解二维泊松方程。给定单位正方形上的 Dirichlet 边值问题,用五点差分格式离散为稀疏线性系统,采用 Jacobi 或共轭梯度迭代求解。项目分阶段交付:先用 OpenMP 实现共享内存版本并测量强/弱扩展;再用 MPI 做一维或二维区域分解,加入 halo 交换;最后尝试混合模式并做性能剖析,提交一份包含加速比曲线、瓶颈分析与优化前后对比的报告。这个项目规模可控,却覆盖了分解、通信、同步、负载均衡、性能评估的完整链条,是理解本课程全部要点的最佳载体。
开始之前,建议准备好一台多核机器(4 核以上即可)、支持 OpenMP 的编译器(如 gcc 加 -fopenmp)以及 MPI 实现(如 OpenMPI)。不必等到"学完再做",从第一个 OpenMP 循环开始就动手测量,让每一次性能数字的变化成为你理解并行原理的最直接反馈。