在 spmd
、parfor
和 parfeval
之间选择
要并行运行计算,您可以使用 spmd
、parfor
、parfeval
或 parfevalOnAll
。每种构造都依赖于不同的并行编程概念。
何时使用 parfor
如果您有一个具有慢速循环迭代或许多循环迭代的 for
循环,则 parfor
循环会很有用。每次迭代必须独立于所有其他迭代。有关何时使用 parfor 的更多帮助,请参阅 决定何时使用 parfor。
何时使用 spmd
运行通信并行代码
如果在计算过程中需要细粒度的工作进程到工作进程通信和工作进程之间的协作,请使用 spmd
。parfor
、parfeval
和 parfevalOnAll
不允许工作进程之间的通信。使用 spmd
的计算可以涉及使用 spmdSend
、spmdReceive
和 spmdSendReceive
函数的工作进程之间的通信。
如果您不确定,请问自己以下问题:在我的并行代码中,每个计算是否可以在没有任何工作进程之间通信的情况下完成?如果是,请使用 parfor
或 parfeval
。否则,使用 spmd
。
在分布式数组上运行并行和定制代码
如果您的计算涉及分布在各个工作进程上的大型数组,请使用 spmd
。您可以对所有工作进程执行同时计算,也可以对特定工作进程执行定制计算。
当工作进程运行 spmd
代码块时,每个工作进程被分配一个唯一的索引,即 spmdIndex
。这使您可以指定仅在某些工作进程和分布式数组的目标部分上运行的代码。
同步和异步工作
在 parfor
、parfeval
和 spmd
之间进行选择时,请考虑您的计算是否需要与客户端同步。
parfor
和 spmd
需要同步,因此阻止您在 MATLAB® 客户端上运行任何新的计算。parfeval
不需要同步,因此您可以继续使用客户端。
使用其他功能
spmd
和 parfeval
具有其他功能,您可以在提交计算后使用。
使用
spmd
,您可以收集spmd
语句内部计算的结果,而无需将结果传输到客户端。从客户端以Composite
对象的形式访问spmd
语句内分配的变量的值。有关详细信息,请参阅使用复合材料访问工作进程变量。当您提交
parfeval
任务时,MATLAB 会安排该任务异步运行,并在提交的任务运行完成之前返回Future
对象。Future
对象代表 MATLAB 已安排的任务。您可以通过不同的方式与Future
对象进行交互:
比较 parfor
、parfeval
和 spmd
的性能
使用 spmd
可能比使用 parfor
循环或 parfeval
更慢或更快,具体取决于计算的类型。开销会影响 parfor
循环、parfeval
和 spmd
的相对性能。
对于一组任务,在以下条件下,parfor
和 parfeval
通常比 spmd
表现更好:
每个任务所花费的计算时间是不确定的。
每个任务所花费的计算时间并不一致。
每个任务返回的数据很少。
在以下情况下使用 parfeval
:
您想在后台运行计算。
每个任务都依赖于其他任务。
在此示例中,您将检查使用 parfor
循环、parfeval
和 spmd
时软件执行矩阵运算的速度。
首先,创建一个进程工作进程并行池 p
。
p = parpool("Processes");
Starting parallel pool (parpool) using the 'Processes' profile ... Connected to parallel pool with 6 workers.
计算随机矩阵
检查软件使用 parfor
循环、parfeval
和 spmd
生成随机矩阵的速度。设置试验次数(n
)和矩阵大小(对于 m
×m
矩阵)。增加试验次数可以改善后续分析中使用的统计数据,但不会影响计算本身。
m = 1500; n = 20;
然后,使用 parfor
循环为每个工作进程执行一次 rand(m)
。对每次 n
试验进行计时。
parforTime = zeros(n,1); for i = 1:n tic; mats = cell(1,p.NumWorkers); parfor N = 1:p.NumWorkers mats{N} = rand(m); end parforTime(i) = toc; end
接下来,使用 parfeval
为每个工作进程执行一次 rand(m)
。对每次 n
试验进行计时。
parfevalTime = zeros(n,1); for i = 1:n tic; f(1:p.NumWorkers) = parallel.FevalFuture; for N = 1:p.NumWorkers f(N) = parfeval(@rand,1,m); end mats = fetchOutputs(f); parfevalTime(i) = toc; clear f end
最后,使用 spmd
为每个工作进程执行一次 rand(m)
。您可以使用 spmdCat
将每个工作进程上的 mat
的值连接成数组 mats
并将其存储在工作进程 1 上。有关工作进程的详细信息以及如何使用 spmd
对它们执行命令,请参阅 在多个数据集上运行单个程序。对每次 n
试验进行计时。
spmdTime = zeros(n,1); for i = 1:n tic; spmd mat = rand(m); mats = spmdCat({mat}, 1, 1); end allMats = mats{1}; spmdTime(i) = toc; end
使用 rmoutliers
从每次试验中删除异常值。然后,使用 boxplot
来比较时间。
% Hide outliers boxData = rmoutliers([parforTime parfevalTime spmdTime]); % Plot data boxplot(boxData, 'labels',{'parfor','parfeval','spmd'}, 'Symbol','') ylabel('Time (seconds)') title('Make n Random Matrices (m-by-m)')
通常,spmd
每次评估所需的开销比 parfor
或 parfeval
要大。因此,在这种情况下,使用 parfor
循环或 parfeval
更有效。
计算随机矩阵的和
接下来,计算随机矩阵的和。您可以通过使用带有 parfor
循环的归约变量、带有 parfeval
的计算后的总和或带有 spmd
的 spmdPlus
来实现此目的。再次设置试验次数(n
)和矩阵大小(对于 m
×m
矩阵)。
m = 1500; n = 20;
然后,使用 parfor
循环为每个工作进程执行一次 rand(m)
。使用归约变量来计算总和。对每次 n
试验进行计时。
parforTime = zeros(n,1); for i = 1:n tic; result = 0; parfor N = 1:p.NumWorkers result = result + rand(m); end parforTime(i) = toc; end
接下来,使用 parfeval
为每个工作进程执行一次 rand(m)
。使用 fetchOutputs
获取所有矩阵,然后使用 sum
。对每次 n
试验进行计时。
parfevalTime = zeros(n,1); for i = 1:n tic; f(1:p.NumWorkers) = parallel.FevalFuture; for N = 1:p.NumWorkers f(N) = parfeval(@rand,1,m); end result = sum(fetchOutputs(f)); parfevalTime(i) = toc; clear f end
最后,使用 spmd
为每个工作进程执行一次 rand(m)
。使用 spmdPlus
对所有矩阵求和。要将结果仅发送给第一个工作进程,请将可选的目标工作进程参量设置为 1
。对每次 n
试验进行计时。
spmdTime = zeros(n,1); for i = 1:n tic; spmd r = spmdPlus(rand(m), 1); end result = r{1}; spmdTime(i) = toc; end
使用 rmoutliers
从每次试验中删除异常值。然后,使用 boxplot
来比较时间。
% Hide outliers boxData = rmoutliers([parforTime parfevalTime spmdTime]); % Plot data boxplot(boxData, 'labels',{'parfor','parfeval','spmd'}, 'Symbol','') ylabel('Time (seconds)') title('Sum of n Random Matrices (m-by-m)')
对于这个计算,spmd
比 parfor
循环或 parfeval
更快。当在 parfor
循环中使用归约变量时,每个工作进程都会执行本地缩减,然后将其部分结果发送回客户端以计算最终结果。
相比之下,spmd
仅调用一次 spmdPlus
来执行全局缩减操作,需要的开销较少。因此,计算减少部分的开销对于 spmd
来说是 ,对于 parfor
来说是 。