主要内容

本页采用了机器翻译。点击此处可查看最新英文版本。

嵌套 parforfor 循环以及其他 parfor 要求

嵌套 parfor 循环

不能在另一个 parfor 循环内使用 parfor 循环。例如,不允许以下 parfor 循环嵌套:

parfor i = 1:10
    parfor j = 1:5
        ...
    end
end

提示

您不能将 parfor 直接嵌套在另一个 parfor 循环中。parfor 循环可以调用包含 parfor 循环的函数,但不会获得任何额外的并行性。

MATLAB® 编辑器中的代码分析器标记了另一个 parfor 循环内 parfor 的使用:

Code analyzer warning stating that PARFOR or SMPD cannot be used inside another PARFOR-loop.

您不能嵌套 parfor 循环,因为并行化只能在一个级别执行。因此,选择要并行运行的循环,并将另一个循环转换为 for 循环。

处理嵌套循环时请考虑以下性能问题:

  • 并行处理会产生开销。一般来说,您应该并行运行外循环,因为开销只发生一次。如果并行运行内部循环,则多个 parfor 执行中的每一个都会产生开销。请参阅将嵌套的 for 循环转换为 parfor 循环 以了解如何测量并行开销的示例。

  • 确保迭代次数超过工作单元数量。否则,您就不会使用所有可用的工作单元。

  • 尝试平衡 parfor 循环迭代时间。parfor 尝试补偿一些负载不平衡。

提示

始终并行运行最外层循环,因为这样可以减少并行开销。

您还可以使用使用 parfor 的函数并将其嵌入 parfor 循环中。并行化仅发生在外层。在下面的示例中,在外部 parfor 循环内调用函数 MyFun.mMyFun.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

下面的代码展示了一个简单的示例。使用 tictoc 来测量所需的计算时间。

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 循环。使用 tictoc 来测量所需的计算时间。使用 ticBytestocBytes 来测量在并行池中从工作单元传输的数据量。

运行新代码,然后再次运行。第一次运行比后续运行慢,因为并行池需要一些时间来启动并让代码可供工作单元使用。

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 循环。原因是工作单元无法启动或访问进一步的并行池。

breakreturn 语句

parfor 循环的主体不能包含 breakreturn 语句。考虑使用 parfevalparfevalOnAll,因为您可以在它们上使用 cancel

全局变量和持久变量

parfor 循环的主体不能包含 globalpersistent 变量声明。原因是这些变量在工作单元之间不同步。您可以在函数中使用 globalpersistent 变量,但它们的值只有创建它们的工作单元才能看到。与使用 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 的输出分配给一个结构体。

narginnargout 函数

parfor 循环内不支持以下用途:

  • 使用不带函数参量的 narginnargout

  • 使用 narginchknargoutchk 来验证当前正在执行的函数调用中的输入或输出参量的数量

原因是工作单元无法访问 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 代码脚本。

另请参阅

| |

主题