4. 并行计算工具箱入门
MATLAB 的并行计算能力并不是“自动生效”的:默认情况下,for 循环仍然在单个工作进程(worker)上顺序执行。要让代码真正并行,需要先建立一个并行池(parallel pool),再使用能够把循环体分发到多个 worker 上的并行语言结构,最常用的就是 parfor。本章围绕并行池的创建与关闭、parfor 的基本语法与限制、以及任务如何被调度到 worker 上展开,目标是让你能独立写出第一个可运行、可验证的并行循环。
一、并行池是什么:worker、parpool 与客户端
并行池由一组独立的 MATLAB 工作进程组成,这些进程称为 worker。你当前操作的 MATLAB 桌面称为客户端(client)。客户端负责提交任务、收集结果;worker 负责实际计算。默认的本地并行池使用本机 CPU 的多个核心,每个 worker 对应一个计算进程,因此并行加速的上限通常受物理核心数限制。
并行池不是越多越好。worker 数量超过物理核心数时,多个 worker 会争抢同一核心,反而增加上下文切换开销。一般建议 parpool 的 worker 数不超过本机物理核心数;对于内存占用大的任务,还要考虑每个 worker 复制一份数据带来的内存压力。
二、启动与关闭并行池
最常用的启动方式是 parpool。不带参数时,MATLAB 会按默认配置启动本地池,worker 数通常等于可用核心数。也可以显式指定 worker 数量:
% 启动 4 个 worker 的本地并行池
pool = parpool(4);
% 查看当前池信息
disp(pool.NumWorkers) % 输出 4
disp(pool.Cluster) % 输出 local
% 使用完毕后关闭
delete(pool);
如果池已经存在,再次调用 parpool 会直接返回现有池,不会重复创建。可以用 gcp(get current pool)获取当前池句柄。关闭池使用 delete(pool);若想关闭当前池而不持有句柄,可用 delete(gcp('nocreate'))。注意 gcp('nocreate') 在无池时返回空,不会自动创建池。
三、parfor 基本语法与适用条件
parfor 的写法与 for 几乎相同,只是把关键字替换掉:
n = 100;
results = zeros(1, n);
parfor i = 1:n
results(i) = sqrt(i) * sin(i);
end
这段代码把 1 到 100 的迭代分配给多个 worker。每个 worker 计算一部分 i,把结果写回 results 的对应位置。关键限制是:循环迭代之间必须相互独立。也就是说,第 i 次迭代不能依赖第 i-1 次迭代的结果,也不能在迭代中修改会被其他迭代读取的共享变量。
常见的违规写法包括:
- 累加式依赖:
s = s + x(i),其中s在循环外初始化并被所有迭代共享。 - 索引依赖:
a(i) = a(i-1) + 1,后一次迭代读取前一次写入的值。 - 动态增长:在循环内用
a(end+1) = ...扩展数组,不同 worker 无法安全协调。
如果确实需要归约操作(求和、求最大值等),应使用 MATLAB 支持的归约变量形式,例如 total = total + x(i) 在 parfor 中是被允许的,因为每个 worker 会维护局部副本,最后再合并。但更推荐用向量化或 sum、max 等函数直接处理,避免依赖隐式归约。
四、任务调度初步:迭代如何被分配
当 parfor 开始执行时,MATLAB 会把迭代区间切分成若干块(chunk),分发给池中的 worker。默认策略是动态调度:先给每个 worker 分配一块,谁先完成谁再领取下一块。这样能缓解不同迭代耗时不一致带来的负载不均。
调度粒度会影响性能。块太小,任务分发和结果回收的通信开销占比升高;块太大,负载均衡效果变差。MATLAB 会根据迭代次数和 worker 数量自动选择,一般不需要手动干预。但在迭代耗时差异极大时,可以通过调整循环结构或使用 parfor 的选项来改善。
for 反而更快。判断依据是:单次迭代计算量是否足够大、迭代之间是否独立、数据量是否适合分发。五、一个可验证的完整示例
下面用一个计算量适中的例子观察并行效果。任务是对每个 i 计算一个较大矩阵的特征值之和,单次迭代耗时约几毫秒到几十毫秒,适合并行。
n = 200;
vals = zeros(1, n);
% 启动池(假设本机 4 核)
pool = parpool(4);
tic;
parfor i = 1:n
A = rand(300) + i * eye(300); % 构造一个随 i 变化的矩阵
vals(i) = sum(eig(A)); % 计算特征值之和
end
t_par = toc;
delete(pool);
% 顺序版本对比
tic;
for i = 1:n
A = rand(300) + i * eye(300);
vals_seq(i) = sum(eig(A));
end
t_seq = toc;
fprintf('并行耗时: %.2f s\n', t_par);
fprintf('顺序耗时: %.2f s\n', t_seq);
fprintf('加速比: %.2f\n', t_seq / t_par);
运行后应观察三点:第一,vals 与 vals_seq 是否一致(在浮点误差范围内);第二,并行耗时是否明显小于顺序耗时;第三,加速比是否接近 worker 数量。如果加速比远低于预期,常见原因是迭代计算量太小、数据分发开销大,或者 worker 数量超过了物理核心数。
六、常见配置与排错要点
| 现象 | 可能原因 | 处理方式 |
|---|---|---|
| parpool 启动失败 | 许可证不含并行工具箱,或 worker 数超过限制 | 用 ver 确认 Parallel Computing Toolbox 已安装;减少 worker 数 |
| parfor 报“变量无法分类” | 循环内使用了不支持的变量类型或索引方式 | 检查是否有跨迭代依赖、动态增长或嵌套 parfor |
| 并行比顺序还慢 | 单次迭代计算量过小,通信开销占主导 | 增大单次迭代计算量,或改回顺序 for |
| 内存不足 | 每个 worker 复制一份大数组 | 减少 worker 数,或改用分布式数组、datastore |