HIT-并行计算 | 当我第一次把一个 for 循环拆给 16 个线程

HIT-Parallel-Computing-Labs

并行计算实验给我的第一个教训是:程序一旦交给多个线程或多个进程,很多原本理所当然的顺序都会消失。一个 printf 可能先后顺序不固定;一个共享计数器,如果没有同步,结果可能每次都不一样;一个 MPI 程序,即使每个进程都算对了,最后也要把数据正确地汇总回来。

这个仓库里有四组实验,从云上集群和 MPI Hello World,到 PThread、互斥量、OpenMP、MPI N-body,再到 CUDA 向量和矩阵运算。它们让我第一次真正体会到,所谓加速不是把代码复制几份,而是要重新思考数据、任务和同步。

仓库地址:HIT-Parallel-Computing-Labs

实验一:先让多个进程在集群上“打个招呼”

第一组实验从云上集群、MPI Hello World 和并行计算 π 开始。最初的 Hello World 很简单:启动多个进程,让它们分别输出自己的编号和信息。

可第一次看到输出顺序时,我还是愣了一下。代码里明明是按某种顺序启动的,终端上却不一定按那个顺序打印。后来我才接受,进程调度和通信时机不是由源代码里的行号决定的。并行程序里,“先启动”不等于“先输出”。

并行计算 π 的实验则让我开始比较串行和并行的时间。采样数量增加以后,估计值通常更稳定;进程数增加以后,计算时间可能下降。但进程不是越多越好,通信和调度也有成本,超过一定规模以后,新增进程可能只是在互相等待。

实验二:PThread、互斥量和 OpenMP,多个线程共享同一个世界

共享内存实验里,我第一次同时接触了 PThread、互斥量和 OpenMP。PThread Hello World 会创建多个线程,输出顺序每次可能不同;这件小事很快把“并发执行”的感觉变得具体。

CountWords 实验更容易暴露问题。多个线程共享文件描述符和统计结果,如果大家同时读、同时改,结果就可能不可靠。pthread_mutex_t 的作用不是让程序变快,而是规定某段共享状态在同一时间只能由一个线程修改。

OpenMP 则把一部分并行工作交给编译器指令来表达。parallelforprivatesharedreductioncritical 这些关键词,背后其实都在回答同一个问题:哪些数据可以各算各的,哪些数据必须同步。

我当时最容易混淆的是“并行”和“安全”。代码能同时跑起来,不代表共享数据就正确;加了锁以后结果正确,也不代表锁的位置合理。锁太多会让线程排队,锁太少则可能出现竞态。

实验三:MPI N-body 和素数计算,通信本身也要花时间

MPI 工作负载把我带回了分布式内存。每个进程都有自己的内存,想要拿到别人的数据,就必须通过消息通信。N-body 模拟和素数计算都让我看到了任务分配、结果收集和同步之间的关系。

N-body 的计算天然有大量相互作用。把粒子分给不同进程以后,每个进程需要知道自己负责的部分,也需要在合适时机拿到其他粒子的信息。任务分得不均,有的进程提前算完,只能等最慢的那个。

素数计算相对直观一些,但同样需要把范围划分开,再把局部结果合起来。并行版本的代码通常比串行版本更长,不是因为计算公式变复杂,而是因为多了任务边界、通信和同步。

实验四:CUDA,第一次让 GPU 参与向量和矩阵运算

CUDA 实验是这组课程里最有“硬件感”的部分。向量加法看起来很简单,但数据从 CPU 内存传到 GPU、启动 kernel、计算完成再把结果传回来,这些步骤都需要时间。

矩阵乘法则让我更明显地看到,计算时间不能只看 GPU 上那段代码。内存传输、线程组织、块大小和数值误差都要一起考虑。一个实现即使计算核心很快,如果数据搬运成本很高,整体加速也可能并不理想。

项目里的 CUDA 代码使用了较早的 API 风格,现代 CUDA 环境可能需要兼容调整。它没有声称在现在的机器上重新跑出新的基准,我更愿意把它看成一次 GPU 编程的历史练习。

四组实验放在一起,我终于明白“并行”不是按下加速键

MPI 让我看到进程之间需要通信;PThread 和 OpenMP 让我处理共享内存和同步;CUDA 则把任务分配到了另一种硬件执行模型上。它们表面上都在追求更快,实际却有不同的瓶颈。

模型主要问题我留下的直觉
MPI 进程之间如何通信和汇总 通信和负载均衡不能被忽略
PThread 共享数据如何同步 线程多不等于结果正确
OpenMP 哪些循环和变量可以并行 并行区域和数据作用域要说清楚
CUDA 线程组织与内存传输 计算加速要看完整数据路径

现在回头看,最值得记住的是那些“不按顺序发生”的瞬间

并行程序最容易让人误以为自己写错了:输出顺序变了,统计值偶尔不同,某次运行比上次慢,某个进程提前结束以后其他进程还在等待。后来我慢慢学会先问,这是不是并发本来就允许发生的情况。

当然,这不意味着把所有不稳定都归因于并行。正确的做法是区分调度造成的非确定性、竞态条件、通信死锁和真正的逻辑错误。实验让我开始用日志、计时和缩小规模的方法,把这些问题一个个拆开。

仓库地址:HIT-Parallel-Computing-Labs。其中不包含 SSH 私钥、云主机配置和第三方工具链,报告里的时间也只是历史实验记录。

说明:本文根据个人历史课程实验和公开仓库整理,部分文字经过 AI 辅助润色;并行与 CUDA 结果属于历史实验记录,部分实验依赖 MPI、CUDA 或外部集群环境。课程资料仅用于学习回顾和个人作品整理,不用于当前课程作业或考试。

posted @ 2026-09-10 22:53  何以牵尘  阅读(5)  评论(0)    收藏  举报