主要内容

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

提高 parfor 性能

您可以通过多种方式提高 parfor 循环的性能。这包括在循环内并行创建数组;分析 parfor 循环;切片数组;以及在集群上运行之前在本地工作单元上优化代码。

在何处创建数组

当您在 parfor 循环之前在客户端创建一个大型数组,并在循环内访问它时,您可能会发现代码执行速度很慢。为了提高性能,告诉每个 MATLAB® 工作单元并行创建自己的数组或数组的一部分。您可以通过要求每个工作单元在循环内并行创建这些数组的副本来节省从客户端向工作单元传输数据的时间。考虑改变在 for 循环之前初始化变量的通常做法,避免循环内不必要的重复。您可能会发现在循环内并行创建数组可以提高性能。

绩效改进取决于不同的因素,包括

  • 数组的大小

  • 创建数组所需的时间

  • 工作单元可以访问全部或部分数组

  • 每个工作单元执行的循环迭代次数

当您考虑将 for 循环转换为 parfor 循环时,请考虑此列表中的所有因素。有关详细信息,请参阅将 for 循环转换为 parfor 循环

作为替代方案,考虑使用 parallel.pool.Constant 函数在循环之前在池工作单元上建立变量。循环结束后,这些变量仍保留在工作单元上,并可用于多个 parfor 循环。您可以使用 parallel.pool.Constant 来提高性能,因为数据只向工作单元传输一次。

在这个示例中,首先创建一个大数据集 D 并执行访问 parforD 循环。然后使用 D 构建 parallel.pool.Constant 对象,该对象允许您通过将 D 复制到每个工作单元来重用数据。使用 tictoc 测量每种情况下的经过时间并记录差异。

function constantDemo
    D = rand(1e7, 1);
    tic
    for i = 1:20
        a = 0;
        parfor j = 1:60
            a = a + sum(D);
        end
    end
    toc
    
    tic
    D = parallel.pool.Constant(D);
    for i = 1:20
        b = 0;
        parfor j = 1:60
            b = b + sum(D.Value);
        end
    end
    toc
end
>> constantDemo
Starting parallel pool (parpool) using the 'Processes' profile ... connected to 4 workers.
Elapsed time is 63.839702 seconds.
Elapsed time is 10.194815 seconds.
在第二种情况下,您只需发送一次数据。您可以使用 parfor 对象来增强 parallel.pool.Constant 循环的性能。

探查 parfor 循环

您可以通过使用 tictoc 测量经过的时间来配置文件 parfor 循环。您还可以使用 ticBytestocBytes 来测量并行池中工作单元之间传输的数据量。请注意,这与使用 MATLAB 探查器以通常方式分析 MATLAB 代码不同,请参阅 探查您的代码以改善性能

此示例计算矩阵的谱半径,并将 for 循环转换为 parfor 循环。测量由此产生的加速和传输的数据量。

  1. 在 MATLAB 编辑器中,输入以下 for 循环。添加 tictoc 来测量经过的时间。将文件保存为 MyForLoop.m

    function a = MyForLoop(A)
        tic
        for i = 1:200
            a(i) = max(abs(eig(rand(A))));
        end
        toc
    end
  2. 运行代码并记录经过的时间。

    a = MyForLoop(500);
    Elapsed time is 31.935373 seconds.

  3. MyForLoop.m 中,将 for 循环替换为 parfor 循环。添加 ticBytestocBytes 来测量在并行池中从工作单元传输的数据量。将文件保存为 MyParforLoop.m

    ticBytes(gcp);
    parfor i = 1:200
        a(i) = max(abs(eig(rand(A))));
    end
    tocBytes(gcp)

  4. 运行新代码,然后再次运行。请注意,第一次运行比第二次运行慢,因为必须启动并行池,并且必须使代码可供工作单元使用。记下第二次运行的经过时间。

    默认情况下,MATLAB 会自动在本地计算机上开启一个并行工作单元池。

    a = MyParforLoop(500);
    Starting parallel pool (parpool) using the 'Processes' profile ... connected to 4 workers.
    ...
                 BytesSentToWorkers    BytesReceivedFromWorkers
                 __________________    ________________________
    
        1        15340                  7024                   
        2        13328                  5712                   
        3        13328                  5704                   
        4        13328                  5728                   
        Total    55324                 24168                   
    
    Elapsed time is 10.760068 seconds. 
    串行运行耗时 31.9 秒,并行运行耗时 10.8 秒,表明此代码可从转换为 parfor 循环中获益。

对数组进行切片

如果变量在 parfor 循环之前初始化,然后在 parfor 循环内使用,则必须将其传递给评估循环迭代的每个 MATLAB 工作单元。只有在循环内使用的变量才会从客户端工作区传递过来。然而,如果所有出现的变量都由循环变量索引,则每个工作单元只会接收它所需的数组部分。

例如,首先使用分段变量运行 parfor 循环并测量经过的时间。

% Sliced version

M = 100;
N = 1e6;
data = rand(M, N);

tic
parfor idx = 1:M
    out2(idx) = sum(data(idx, :)) ./ N;
end
toc
Elapsed time is 2.261504 seconds.

现在假设您在 data 循环内意外使用了对变量 N 而不是 parfor 的引用。这里的问题是,对 size(data, 2) 的调用将分段变量转换为广播(非分段)变量。

% Accidentally non-sliced version

clear

M = 100;
N = 1e6;
data = rand(M, N);

tic
parfor idx = 1:M
    out2(idx) = sum(data(idx, :)) ./ size(data, 2);
end
toc
Elapsed time is 8.369071 seconds.
请注意,意外广播的变量所用的时间更长。

在这种情况下,您可以轻松避免 data 的非切片使用,因为结果是一个常量,并且可以在循环外计算。一般来说,您可以在循环开始之前执行仅依赖于广播数据的计算,因为广播数据无法在循环内部被修改。在这种情况下,计算很简单,并产生标量结果,因此您可以从循环中取出计算。

优化本地和集群工作单元

在本地工作单元上运行代码可能会带来测试应用程序的便利,而无需使用集群资源。然而,使用本地工作单元也存在某些缺点或限制。由于数据的传输不是通过网络进行的,因此本地工作单元的传输行为可能无法表明它通常如何通过网络发生。

对于本地工作单元来说,由于所有 MATLAB 工作单元程序会话都在同一计算机上运行,因此您可能不会看到 parfor 循环在执行时间方面有任何性能改进。这取决于许多因素,包括您的计算机有多少个处理器和核心。这里的关键点是集群可能比本地计算机拥有更多的可用核心。如果您的代码可以由 MATLAB 进行多线程处理,那么加快速度的唯一方法是使用集群,用更多的内核来处理问题。

您可以进行试验,看看在循环之前创建数组是否更快(如下图左侧所示),而不是让每个工作单元在循环内创建自己的数组(如右图所示)。

尝试在本地运行并行池的以下示例,并注意每个循环的执行时间差异。首先开启本地并行池:

parpool('Processes')

运行下面的示例,然后再次执行。请注意,每种情况的第一次运行都比第二次运行慢,因为必须启动并行池,并且必须使代码可供工作单元使用。记录每种情况第二次运行所用的时间。

tic;
n = 200;
M = magic(n);
R = rand(n);
parfor i = 1:n
    A(i) = sum(M(i,:).*R(n+1-i,:));
end
toc
tic;
n = 200;
parfor i = 1:n
    M = magic(n);
    R = rand(n);
    A(i) = sum(M(i,:).*R(n+1-i,:));
end
toc

在远程集群上运行时,您可能会发现不同的行为,因为工作单元可以同时创建它们的数组,从而节省传输时间。因此,针对本地工作单元优化的代码可能不会针对集群工作单元进行优化,反之亦然。

另请参阅

主题