嵌套 parfor
和 for
循环以及其他 parfor
要求
嵌套 parfor
循环
不能在另一个 parfor
循环内使用 parfor
循环。例如,不允许以下 parfor
循环嵌套:
parfor i = 1:10 parfor j = 1:5 ... end end
提示
您不能将 parfor
直接嵌套在另一个 parfor
循环中。parfor
循环可以调用包含 parfor
循环的函数,但不会获得任何额外的并行性。
MATLAB® 编辑器中的代码分析器标记了另一个 parfor
循环内 parfor
的使用:
您不能嵌套 parfor
循环,因为并行化只能在一个级别执行。因此,选择要并行运行的循环,并将另一个循环转换为 for
循环。
处理嵌套循环时请考虑以下性能问题:
并行处理会产生开销。一般来说,您应该并行运行外循环,因为开销只发生一次。如果并行运行内部循环,则多个
parfor
执行中的每一个都会产生开销。请参阅将嵌套的 for 循环转换为 parfor 循环 以了解如何测量并行开销的示例。确保迭代次数超过工作单元数量。否则,您就不会使用所有可用的工作单元。
尝试平衡
parfor
循环迭代时间。parfor
尝试补偿一些负载不平衡。
提示
始终并行运行最外层循环,因为这样可以减少并行开销。
您还可以使用使用 parfor
的函数并将其嵌入 parfor
循环中。并行化仅发生在外层。在下面的示例中,在外部 parfor
循环内调用函数 MyFun.m
。MyFun.m
中嵌入的内部 parfor
循环按顺序运行,而不是并行。
parfor i = 1:10 MyFun(i) end function MyFun(i) parfor j = 1:5 ... end end
提示
嵌套的 parfor
循环通常不会给您带来任何计算上的好处。
将嵌套的 for
循环转换为 parfor
循环
嵌套循环的典型用法是使用单循环变量来索引一个维度,并使用嵌套循环变量来索引另一个维度来遍历数组。基本形式为:
X = zeros(n,m); for a = 1:n for b = 1:m X(a,b) = fun(a,b) end end
下面的代码展示了一个简单的示例。使用 tic
和 toc
来测量所需的计算时间。
A = 100; tic for i = 1:100 for j = 1:100 a(i,j) = max(abs(eig(rand(A)))); end end toc
Elapsed time is 49.376732 seconds.
您可以并行化任一嵌套循环,但不能并行运行两者。原因是并行池中的工作单元无法启动或访问进一步的并行池。
如果将 i
计数的循环转换为 parfor
循环,则池中的每个工作单元都会使用 j
循环计数器执行嵌套循环。j
循环本身不能在每个工作单元上作为 parfor
运行。
由于并行处理会产生开销,因此您必须谨慎选择是否要将内部或外部 for
循环转换为 parfor
循环。下面的示例显示了如何测量并行开销。
首先仅将外部 for
循环转换为 parfor
循环。使用 tic
和 toc
来测量所需的计算时间。使用 ticBytes
和 tocBytes
来测量在并行池中从工作单元传输的数据量。
运行新代码,然后再次运行。第一次运行比后续运行慢,因为并行池需要一些时间来启动并让代码可供工作单元使用。
A = 100; tic ticBytes(gcp); parfor i = 1:100 for j = 1:100 a(i,j) = max(abs(eig(rand(A)))); end end tocBytes(gcp) toc
BytesSentToWorkers BytesReceivedFromWorkers __________________ ________________________ 1 32984 24512 2 33784 25312 3 33784 25312 4 34584 26112 Total 1.3514e+05 1.0125e+05 Elapsed time is 14.130674 seconds.
接下来仅将内部循环转换为 parfor
循环。像前一种情况一样测量所需时间和传输的数据。
A = 100; tic ticBytes(gcp); for i = 1:100 parfor j = 1:100 a(i,j) = max(abs(eig(rand(A)))); end end tocBytes(gcp) toc
BytesSentToWorkers BytesReceivedFromWorkers __________________ ________________________ 1 1.3496e+06 5.487e+05 2 1.3496e+06 5.4858e+05 3 1.3677e+06 5.6034e+05 4 1.3476e+06 5.4717e+05 Total 5.4144e+06 2.2048e+06 Elapsed time is 48.631737 seconds.
如果将内循环转换为 parfor
循环,则传输的时间和数据量都会比并行外循环大得多。在这种情况下,经过的时间几乎与嵌套的 for
循环示例中的时间相同。与并行运行外循环相比,加速比更小,因为有更多的数据传输,因此有更多的并行开销。因此,如果并行执行内部循环,与运行串行 for
循环相比,不会获得任何计算优势。
如果您想减少并行开销并加快计算速度,请并行运行外循环。
如果您转换内部循环,则外部循环的每次迭代都会启动单独的 parfor
循环。也就是说,内循环转换创建了 100 个 parfor
循环。每次 parfor
执行都会产生开销。如果您想减少并行开销,您应该并行运行外循环,因为开销只发生一次。
提示
如果您想加快代码速度,请始终并行运行外循环,因为这样可以减少并行开销。
嵌套的 for
循环:要求和限制
如果您想将嵌套的 for
循环转换为 parfor
循环,则必须确保循环变量被正确分类,请参阅 排除 parfor 循环中的变量故障。如果您的代码不遵守标记为 Required 的准则和限制,您将收到错误。MATLAB 在读取代码时会捕获其中一些错误。这些错误被标记为必需(静态)。
必需(静态):您必须通过常数或广播变量定义嵌套在 parfor 循环中的 for 循环的范围。 |
在下面的示例中,左边的代码不起作用,因为您通过函数调用定义了 for
循环的上限。右侧的代码提供了一种解决方法,首先在 parfor
循环外定义一个广播或常量变量:
无效 | 有效 |
---|---|
A = zeros(100, 200); parfor i = 1:size(A, 1) for j = 1:size(A, 2) A(i, j) = i + j; end end | A = zeros(100, 200); n = size(A, 2); parfor i = 1:size(A,1) for j = 1:n A(i, j) = i + j; end end |
必需(静态):嵌套 for 循环的索引变量绝不能通过其 for 语句以外的方式明确分配。 |
必须遵守此限制。如果嵌套的 for
循环变量在 parfor
循环中的任何地方(除其 for
语句之外)发生更改,则不能保证 for
循环变量索引的区域在每个工作单元上都可用。
左边的代码无效,因为它试图修改循环主体中嵌套的 for
循环变量 j
值。右侧的代码提供了一种解决方法,即将嵌套的 for
循环变量分配给临时变量 t
,然后更新 t
。
无效 | 有效 |
---|---|
A = zeros(10); parfor i = 1:10 for j = 1:10 A(i, j) = 1; j = j+1; end end | A = zeros(10); parfor i = 1:10 for j = 1:10 A(i, j) = 1; t = j; t = t + 1; end end |
必需(静态):您不能对嵌套的 for 循环变量进行索引或下标。 |
必须遵守此限制。如果对嵌套的 for
循环变量进行索引,则不能保证迭代是独立的。
左边的示例是无效的,因为它试图索引嵌套的 for
循环变量 j
。右边的示例删除了这个索引。
无效 | 有效 |
---|---|
A = zeros(10); parfor i = 1:10 for j = 1:10 j(1); end end | A = zeros(10); parfor i = 1:10 for j = 1:10 j; end end |
必需(静态):当使用嵌套的 for 循环变量来索引切片数组时,必须以纯文本形式使用该变量,而不是作为表达式的一部分。 |
例如,以下左侧的代码不起作用,但右侧的代码起作用:
无效 | 有效 |
---|---|
A = zeros(4, 11); parfor i = 1:4 for j = 1:10 A(i, j + 1) = i + j; end end | A = zeros(4, 11); parfor i = 1:4 for j = 2:11 A(i, j) = i + j - 1; end end |
必需(静态):如果使用嵌套的 for 循环来索引切片数组,则不能在 parfor 循环的其他地方使用该数组。 |
在下面的示例中,左边的代码不起作用,因为 A
在嵌套的 for
循环内被切片和索引。右边的代码能够正常工作,因为在嵌套循环之外将 v
分配给了 A
:
无效 | 有效 |
---|---|
A = zeros(4, 10); parfor i = 1:4 for j = 1:10 A(i, j) = i + j; end disp(A(i, j)) end | A = zeros(4, 10); parfor i = 1:4 v = zeros(1, 10); for j = 1:10 v(j) = i + j; end disp(v(j)) A(i, :) = v; end |
parfor
循环限制
嵌套函数
parfor
循环的主体不能引用嵌套函数。但是,它可以通过函数句柄调用嵌套函数。尝试以下示例。请注意,A(idx) = nfcn(idx)
循环中的 parfor
不起作用。您必须使用 feval
来调用 fcn
循环体中的 parfor
句柄。
function A = pfeg function out = nfcn(in) out = 1 + in; end fcn = @nfcn; parfor idx = 1:10 A(idx) = feval(fcn, idx); end end
>> pfeg Starting parallel pool (parpool) using the 'Processes' profile ... connected to 4 workers. ans = 2 3 4 5 6 7 8 9 10 11
提示
如果您使用引用 parfor
循环内的嵌套函数的函数句柄,则外部范围变量的值将不会在工作单元之间同步。
嵌套 parfor
循环
parfor
循环的主体不能包含 parfor
循环。有关详细信息,请参阅嵌套 parfor 循环。
嵌套 spmd
语句
parfor
循环的主体不能包含 spmd
语句,而 spmd
语句不能包含 parfor
循环。原因是工作单元无法启动或访问进一步的并行池。
break
和 return
语句
parfor
循环的主体不能包含 break
或 return
语句。考虑使用 parfeval
或 parfevalOnAll
,因为您可以在它们上使用 cancel
。
全局变量和持久变量
parfor
循环的主体不能包含 global
或 persistent
变量声明。原因是这些变量在工作单元之间不同步。您可以在函数中使用 global
或 persistent
变量,但它们的值只有创建它们的工作单元才能看到。与使用 global
变量相比,使用函数参量来共享值是一种更好的做法。
要了解有关变量要求的更多信息,请参阅 排除 parfor 循环中的变量故障。
脚本
如果脚本引入了变量,则无法从 parfor
循环或 spmd
语句中调用该脚本。原因是该脚本会导致透明度违规。有关详细信息,请参阅确保 parfor 循环或 spmd 语句的透明度。
匿名函数
您可以在 parfor
循环体内定义一个匿名函数。但是,不支持匿名函数内的分段输出变量。您可以通过对分段变量使用临时变量来解决此问题,如以下示例所示。
x = 1:10; parfor i=1:10 temp = x(i); anonymousFunction = @() 2*temp; x(i) = anonymousFunction() + i; end disp(x);
有关分段变量的更多信息,请参阅 分段变量。
inputname
函数
在 parfor
循环内不支持使用 inputname
返回与参量编号相对应的工作区变量名称。原因是 parfor
工作单元无法访问 MATLAB 桌面的工作区。为了解决此问题,请在 parfor
之前调用 inputname
,如以下示例所示。
a = 'a'; myFunction(a) function X = myFunction(a) name = inputname(1); parfor i=1:2 X(i).(name) = i; end end
load
函数
parfor
循环内不支持未分配给输出结构体的 load
语法。在 parfor
内部,始终将 load
的输出分配给一个结构体。
nargin
或 nargout
函数
parfor
循环内不支持以下用途:
使用不带函数参量的
nargin
或nargout
使用
narginchk
或nargoutchk
来验证当前正在执行的函数调用中的输入或输出参量的数量
原因是工作单元无法访问 MATLAB 桌面的工作区。为了解决此问题,请在 parfor
之前调用这些函数,如以下示例所示。
myFunction('a','b') function X = myFunction(a,b) nin = nargin; parfor i=1:2 X(i) = i*nin; end end
P 代码脚本
您可以从 parfor
循环内调用 P 代码脚本文件,但 P 代码脚本不能包含 parfor
循环。要解决此问题,请使用 P 代码函数而不是 P 代码脚本。
另请参阅
parfor
| parfeval
| parfevalOnAll