主要内容

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

嵌套 parfor 和 for 循环以及其他 parfor 要求

R2025b

嵌套 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.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 代码脚本。

另请参阅

| |

主题