在大数据和 AI 爆发的时代,我们经常会遇到 “数据太多、计算太慢” 的问题 —— 比如训练一个 AI 模型要几天,分析一次全量用户数据要几小时。这时候,“串行算法”(一步一步做计算)就像一个人搬砖,再努力也赶不上进度;而 “并行算法” 就像一群人一起搬砖,效率能翻好几倍。今天我们就从《并行计算:结构・算法・编程》第五章出发,聊聊并行算法的基础知识,看看它是怎么让计算 “提速” 的。
5.1 并行算法的基础知识
在聊并行算法之前,我们得先搞懂:什么是算法?并行算法和我们平时接触的 “串行算法” 有什么区别?这就从最基础的定义和分类说起。
5.1.1 并行算法的定义和分类:给计算 “分分工”
1. 先搞懂:算法 vs 并行算法
- 算法:简单说就是 “解决问题的步骤”,比如算 1+2+3+…+100,串行算法是 “先算 1+2=3,再算 3+3=6,一直加到 100”,一步接一步,就像一个人按顺序做事;
- 并行算法:把 “步骤拆成多份,让多个‘计算单元’(比如 CPU 核心、服务器)同时做”,比如算 1+2+…+100,并行算法可以让 2 个人同时算:甲算 1-50 的和,乙算 51-100 的和,最后把两人的结果加起来,速度直接翻倍。
核心区别:串行算法是 “时间上的先后”,并行算法是 “空间上的同时”—— 就像煮饺子,串行是 “煮一锅捞出来再煮下一锅”,并行是 “用两个锅同时煮”,效率天差地别。
2. 并行算法的分类:按 “任务类型” 和 “工作方式” 分
按不同维度,并行算法能分成好几类,我们挑最常用的几类聊聊:
按计算任务类型分:数值计算 vs 非数值计算
- 数值计算并行算法:处理 “带数字运算” 的问题,比如矩阵乘法、解方程、求积分。比如天气预报时,要计算大气中每个点的温度、气压变化,这就是数值计算,用并行算法让多个服务器同时算不同区域的数据,能把预报时间从几小时缩短到几十分钟;
- 非数值计算并行算法:处理 “不用复杂数字运算” 的问题,比如排序、搜索、图论(比如找最短路径)。比如电商平台给 1 亿个用户排序(按消费金额),串行算法要逐个比较,太慢;并行算法可以把 1 亿个用户分成 100 份,每个处理器排 1000 万用户,最后再合并结果,几秒钟就能搞定。
按同步方式分:同步算法 vs 异步算法
- 同步算法:所有计算单元 “按统一节奏干活”,就像接力赛,第一棒跑完才能交棒给第二棒,没人能提前或落后。比如计算 1-1000 的和,分 10 个处理器各算 100 个数,必须等所有处理器都算完自己的部分,才能汇总结果 —— 要是有一个处理器算得慢,其他人都得等它。这种算法的好处是 “不容易出错”,但缺点是 “效率受限于最慢的单元”(也就是常说的 “木桶短板”);
- 异步算法:每个计算单元 “按自己的速度干活,不用等别人”,算完自己的部分就把结果交给汇总单元,比如小组分工做报告,有人写引言,有人查数据,有人做 PPT,不用等所有人都做完一步再做下一步,谁先做完谁就推进下一个任务。这种算法的好处是 “灵活高效”,但缺点是 “需要处理数据冲突”—— 比如两个人同时改报告的同一部分,容易出错。
其他常用分类
- 分布算法:计算单元分散在不同地方(比如不同城市的服务器),通过网络通信协作,比如云计算里的任务分配,北京的服务器算用户数据,上海的服务器算商品数据,最后通过网络汇总;
- 确定算法 vs 随机算法:确定算法的 “步骤固定,结果唯一”,比如算 1+2+3,不管用多少次,结果都是 6;随机算法会 “引入概率步骤”,比如用蒙特卡洛方法算圆周率,每次计算结果可能不一样,但会逐渐接近真实值,适合解决复杂的不确定性问题。
5.1.2 并行算法的表达:怎么 “告诉” 计算机 “同时做”
串行算法用 “if-else”“for 循环” 就能表达,比如 “for i=1 to 100:sum=sum+i”;但并行算法要表达 “多个任务同时做”,就得用专门的语句,最常用的就是 “par-do 语句” 和 “for all 语句”。
1. par-do 语句:“同时执行多个独立任务”
par-do 的意思是 “parallel do”(并行执行),语法大概是 “par-do { 任务 1;任务 2;任务 3 }”,表示花括号里的几个任务要 “同时做”,谁先做完谁先结束,不用等别人。
举个生活例子:家里打扫卫生,要做三件事 “擦桌子、拖地、洗碗”,用 par-do 语句表达就是:
par-do {
擦桌子(任务1);
拖地(任务2);
洗碗(任务3);
}
这就相当于三个人同时做这三件事,10 分钟就能做完;要是用串行的 for 循环,就得先擦桌子(5 分钟),再拖地(5 分钟),再洗碗(5 分钟),总共 15 分钟。
再举个计算例子:计算三个数的平方(a²、b²、c²),并行算法用 par-do:
par-do {
a_sq = a * a; // 任务1:算a的平方
b_sq = b * b; // 任务2:算b的平方
c_sq = c * c; // 任务3:算c的平方
}
sum = a_sq + b_sq + c_sq; // 等三个任务都做完,再求和
这里三个平方计算同时进行,比串行 “先算 a²,再算 b²,再算 c²” 快 3 倍(不考虑汇总时间)。
2. for all 语句:“对所有元素同时执行同一个操作”
for all 的意思是 “for all elements”(对所有元素),语法大概是 “for all i in 1..n:任务 (i)”,表示对 “1 到 n” 的每个 i,都同时执行 “任务 (i)”,适合 “对一组数据做相同操作” 的场景。
举个生活例子:老师给全班 50 个同学发试卷,用 for all 语句表达就是:
for all 同学i in 1..50:
给同学i发试卷;
这就相当于 50 个老师同时给每个同学发试卷,1 秒钟就能发完;要是用串行的 for 循环,一个老师逐个发,得 50 秒钟。
再举个计算例子:给数组 arr [1..5] 的每个元素乘 2(也就是 arr [i] = arr [i] * 2),并行算法用 for all:
for all i in 1..5:
arr[i] = arr[i] * 2;
这表示 5 个计算单元同时处理 arr [1] 到 arr [5],1 步就能完成;要是串行 for 循环,得一步处理一个元素,总共 5 步。
关键区别:par-do 处理 “不同的独立任务”(比如擦桌子、拖地、洗碗),for all 处理 “对不同数据做相同任务”(比如给每个同学发试卷、给每个数组元素乘 2)。
5.1.3 并行算法的复杂性度量:怎么判断 “并行算法好不好”
评价串行算法,我们看 “时间复杂度”(比如 O (n)、O (nlogn)),表示数据量 n 变大时,计算时间怎么增长;但评价并行算法,光看时间不够,还得看 “用了多少处理器”“通信开销有多大”,因为并行不是 “处理器越多越好”—— 比如用 1000 个处理器算 100 个数的和,大部分时间都在等通信,反而比 10 个处理器慢。
1. 复杂度的渐近表示:“忽略细节,看趋势”
不管串行还是并行,复杂度都常用 “渐近表示”(比如 O、Ω、Θ),核心是 “当数据量 n 足够大时,忽略常数和低阶项,只看最高阶项”。比如一个算法的时间是 “3n + 5”,渐近表示就是 O (n),因为当 n=1000 时,31000=3000,5 可以忽略;当 n=10000 时,310000=30000,5 更不用提。
常见的渐近复杂度从快到慢排序:O (1)(常数时间,比如取数组第一个元素)< O (logn)(对数时间,比如二分查找)< O (n)(线性时间,比如遍历数组)< O (nlogn)(线性对数时间,比如快速排序)< O (n²)(平方时间,比如冒泡排序)。
2. 并行算法的复杂性度量:三个核心指标
并行算法的复杂度不能只看 “时间”,要结合 “处理器数量” 和 “通信开销”,常用三个指标:
- 并行时间(T_p):用 p 个处理器完成任务的总时间,比如用 4 个处理器算 1-100 的和,花了 5 秒,T_p=5;
- 处理器数量(p):参与计算的处理器个数,比如 4 个、10 个、100 个;
- 总工作量(W):所有处理器的计算时间之和,也就是 “并行时间 * 处理器数量”(假设每个处理器都满负荷工作),比如 4 个处理器各算 5 秒,总工作量 W=4*5=20。
好的并行算法要满足 “总工作量接近串行算法的时间(T_s)”,也就是 “W ≈ T_s”—— 比如串行算 1-100 的和要 20 秒(T_s=20),并行用 4 个处理器花 5 秒(T_p=5),总工作量 W=20,和 T_s 差不多,这就是高效的;要是用 10 个处理器花 5 秒(T_p=5),总工作量 W=50,比 T_s 大很多,说明很多处理器在 “闲置等通信”,效率低。
另外还要考虑 “通信开销(C)”:处理器之间传递数据的时间,比如分布式系统里,北京的处理器要给上海的处理器传数据,花了 2 秒,这 2 秒就是通信开销。通信开销太大,会抵消并行带来的速度提升 —— 比如用 4 个处理器算和,并行计算只要 3 秒,但通信花了 2 秒,总时间 5 秒,反而不如用 2 个处理器(计算 4 秒,通信 1 秒,总时间 5 秒,还少用 2 个处理器)。
5.1.4 并行算法中的同步与通信:避免 “各干各的出乱子”
并行算法不是 “把任务拆了就完事”,关键是要解决两个问题:“同步”(让大家按节奏来,别乱)和 “通信”(让大家交换数据,别断)。这就像团队合作,既要 “统一进度”,又要 “互通信息”,否则会出大问题。
1. 并行算法中的同步:“等所有人都到齐再走”
同步就是 “让所有处理器在某个节点等待,直到所有人都完成当前步骤,再一起进入下一步”,避免 “有的处理器快,有的慢,导致数据不一致”。
例子:共享存储多处理器上的求和算法
假设我们要计算数组 A [1..8] 的和(A=[1,2,3,4,5,6,7,8]),用 4 个处理器(P1-P4),共享一个内存(所有处理器都能读写字节),步骤如下:
- 拆分任务:P1 算 A [1]+A [2],P2 算 A [3]+A [4],P3 算 A [5]+A [6],P4 算 A [7]+A [8]—— 这一步 4 个处理器同时算,得到 4 个中间结果:3(1+2)、7(3+4)、11(5+6)、15(7+8);
- 同步等待:必须等 P1-P4 都算完中间结果,才能进入下一步 —— 要是 P1 算得快(1 秒),P4 算得慢(2 秒),P1 得等 P4 1 秒,否则 P1 直接进入下一步,会拿不到 P4 的结果,求和就错了;
- 再拆分汇总:同步后,P1 算 3+7,P2 算 11+15,得到 10 和 26;
- 再同步等待:等 P1 和 P2 算完,P1 再算 10+26,得到最终结果 36。
这里的 “同步等待” 就是关键 —— 没有同步,处理器进度不一,会导致数据缺失或错误。就像小组开会,必须等所有人都到齐再开始,否则少一个人,讨论的结果就不完整。
2. 并行算法中的通信:“把我的数据传给你”
通信就是 “处理器之间交换数据”,主要发生在 “分布存储多计算机”(每个处理器有自己的内存,不能直接访问别人的内存,只能通过网络传数据)中。
例子:分布存储多计算机上的矩阵向量乘算法
假设我们要计算矩阵 A(4 行 4 列)和向量 x(4 个元素)的乘积 y=A*x,用 2 个处理器(P1、P2),每个处理器有自己的内存:
- P1 存 A 的前 2 行(A11-A14、A21-A24)和 x 的前 2 个元素(x1、x2);
- P2 存 A 的后 2 行(A31-A34、A41-A44)和 x 的后 2 个元素(x3、x4)。
但计算 y 的每个元素需要 x 的所有元素(比如 y1=A11x1+A12x2+A13x3+A14x4),P1 只有 x1、x2,没有 x3、x4,这时候就需要通信:
- 通信阶段:P1 把 x1、x2 传给 P2,P2 把 x3、x4 传给 P1—— 这一步花的时间就是通信开销;
- 计算阶段:P1 用 A 的前 2 行和完整的 x,算 y1、y2;P2 用 A 的后 2 行和完整的 x,算 y3、y4;
- 再通信阶段:P1 把 y1、y2 传给 P2,P2 把 y3、y4 传给 P1,汇总得到完整的 y 向量。
这里的通信是 “必须的”—— 没有通信,处理器拿不到完整的数据,根本算不出正确结果。但通信开销要尽量小,比如要是 x 有 1000 个元素,每次传 1000 个数据要花 1 秒,那通信时间会比计算时间还长,并行效率就低了。
写在最后
并行算法的核心不是 “越多处理器越好”,而是 “合理拆分任务、控制同步节奏、减少通信开销”—— 就像组织一场运动会,既要把项目拆给不同的人,又要让大家按赛程走,还要确保信息传递顺畅(比如通知运动员上场),否则再多人也办不好。
现在我们用的手机(多核心 CPU)、电脑(多核处理器)、云计算(多服务器集群),背后都有并行算法在工作 —— 比如手机同时开微信、抖音、浏览器,就是 CPU 的多个核心并行处理不同 APP 的任务;云计算分析全量用户数据,就是多服务器并行计算。
你有没有遇到过 “计算太慢” 的情况?比如导出大量数据要等很久,或者训练 AI 模型花了好几天 —— 其实这些场景都可以用并行算法提速。欢迎在评论区聊聊你的经历,我们一起探讨怎么用并行算法解决!
863

被折叠的 条评论
为什么被折叠?



