使用parfor节省时间和内存?

use*_*148 18 parallel-processing optimization matlab parfor

prova.mat在MATLAB中考虑以下列方式获得

for w=1:100
    for p=1:9    
        A{p}=randn(100,1); 
    end
    baseA_.A=A;

    eval(['baseA.A' num2str(w) '= baseA_;'])

end

save(sprintf('prova.mat'),'-v7.3', 'baseA')
Run Code Online (Sandbox Code Playgroud)

为了了解我的数据中的实际维度,1x9 cellin A1由以下9数组组成:904x5, 913x5, 1722x5, 4136x5, 9180x5, 3174x5, 5970x5, 4455x5, 340068x5.另一个Aj有类似的构成.

请考虑以下代码

clear all
load prova
tic
parfor w=1:100
       indA=sprintf('A%d', w);
       Aarr=baseA.(indA).A;
       Boot=[];
       for p=1:9
           C=randn(100,1).*Aarr{p};
           Boot=[Boot; C];  
       end
       D{w}=Boot;
end
toc
Run Code Online (Sandbox Code Playgroud)

如果我在我的Macbook Pro中parfor使用4本地工作人员运行循环,则需要1.2秒.parforfor它替换需要0.01秒.

根据我的实际数据,时间差为31秒对7秒[矩阵的创建C也更复杂].

如果已正确理解问题是计算机必须发送baseA给每个本地工作人员,这需要时间和内存.

您能否提出一个能够parfor比方便更方便的解决方案for?我认为保存所有单元格baseA是一种通过在开始时加载一次来节省时间的方法,但也许我错了.

Adr*_*aan 35

一般信息

基本上,parfor建议在两种情况下:循环中的大量迭代(即,类似for),或者每次迭代需要很长时间(例如parfor).在第二种情况下,您可能需要考虑使用parfor(比1e10我的经验慢).原因eig(magic(1e4))spmd短距离循环慢或快速迭代是正确管理所有工作人员所需的开销,而不是仅仅进行计算.

此外,许多函数都内置了隐式多线程,因此parfor在使用这些函数时,循环不会比串行parfor循环更高效,因为所有核心都已被使用.for在这种情况下实际上是有害的,因为它具有分配开销,同时与您尝试使用的功能并行.

有关在不同工作人员之间拆分数据的信息,请查看此问题.

标杆

请考虑以下示例,以查看for与之相反的行为parfor.首先打开并行池,如果你还没有这样做:

gcp; % Opens a parallel pool using your current settings
Run Code Online (Sandbox Code Playgroud)

然后执行几个大循环:

n = 1000; % Iteration number
EigenValues = cell(n,1); % Prepare to store the data
Time = zeros(n,1);
for ii = 1:n
tic
    EigenValues{ii,1} = eig(magic(1e3)); % Might want to lower the magic if it takes too long
Time(ii,1) = toc; % Collect time after each iteration
end

figure; % Create a plot of results
plot(1:n,t)
title 'Time per iteration'
ylabel 'Time [s]'
xlabel 'Iteration number[-]';
Run Code Online (Sandbox Code Playgroud)

然后用parfor相反的方法做同样的事情for.您会注意到每次迭代的平均时间会增加(对于我的情况,为0.27s至0.39s).然而,要意识到已parfor使用的所有可用工作程序,因此总时间(sum(Time))必须除以计算机中的核心数.所以对于我的情况,总时间从大约270s下降到49s,因为我有一个octacore处理器.

因此,虽然每次单独迭代的时间parfor随着使用而增加for,但总时间显着下降.

结果

parforbenchtest

这张照片显示了我在家用电脑上运行测试的结果.我曾经n=1000eig(500); 我的电脑有一个I5-750 2.66GHz处理器,带有四个内核,运行MATLAB R2012a.正如你所看到的那样,并行测试的平均值大约在0.29s左右徘徊,而且序列代码相当稳定在0.24s左右.然而,总时间从234秒下降到72秒,这是3.25倍的加速.这不是4的原因是内存开销,如每次迭代所花费的额外时间所表示的那样.内存开销是由于MATLAB必须检查每个内核正在做什么,并确保每次循环迭代只执行一次,并且数据被放入正确的存储位置.


Ole*_*leg 12

将广播数据切片成单元阵列

以下方法适用于按组循环的数据.分组变量是什么并不重要,只要它在循环之前确定即可.速度优势是巨大的.

这样的简化示例data如下,第一列包含分组变量:

ngroups = 1000;
nrows   = 1e6;
data    = [randi(ngroups,[nrows,1]), randn(nrows,1)];
data(1:5,:)
ans =
          620     -0.10696
          586      -1.1771
          625       2.2021
          858      0.86064
           78       1.7456
Run Code Online (Sandbox Code Playgroud)

现在,为简单起见,我想对sum()第二列中的值组感兴趣.我可以按组循环,索引感兴趣的元素并总结它们.我将与执行此任务for循环,一个普通的parforparfor切片数据,并会比较计时.

请记住,这是一个玩具示例,我对替代解决方案不感兴趣bsxfun(),这不是分析的重点.

结果

借用Adriaan的相同类型的情节,我首先确认了关于普通parforvs 的相同发现for.其次,这两种方法都完全优于parfor上切片数据,这需要位2秒以上在数据集中完成1000万行(在切片操作被包括在定时).平原parfor需要24秒才能完成,for几乎是两倍的时间(我在Win7 64,R2016a和i5-3570有4个核心).

在此输入图像描述

在开始之前切片数据的要点parfor是避免:

  • 整个数据的开销正在向工人广播,
  • 将操作索引到不断增长的数据集中.

代码

ngroups = 1000;
nrows   = 1e7;
data    = [randi(ngroups,[nrows,1]), randn(nrows,1)];

% Simple for
[out,t] = deal(NaN(ngroups,1));
overall = tic;
for ii = 1:ngroups
    tic
    idx     = data(:,1) == ii;
    out(ii) = sum(data(idx,2));
    t(ii)   = toc;
end
s.OverallFor = toc(overall);
s.TimeFor    = t;
s.OutFor     = out;

% Parfor
try parpool(4); catch, end
[out,t] = deal(NaN(ngroups,1));
overall = tic;
parfor ii = 1:ngroups
    tic
    idx     = data(:,1) == ii;
    out(ii) = sum(data(idx,2));
    t(ii)   = toc;
end
s.OverallParfor = toc(overall);
s.TimeParfor    = t;
s.OutParfor     = out;

% Sliced parfor
[out,t] = deal(NaN(ngroups,1));
overall = tic;
c       = cache2cell(data,data(:,1));
s.TimeDataSlicing = toc(overall);
parfor ii = 1:ngroups
    tic
    out(ii) = sum(c{ii}(:,2));
    t(ii)   = toc;
end
s.OverallParforSliced = toc(overall);
s.TimeParforSliced    = t;
s.OutParforSliced     = out;

x = 1:ngroups;
h = plot(x, s.TimeFor,'xb',x,s.TimeParfor,'+r',x,s.TimeParforSliced,'.g');
set(h,'MarkerSize',1)
title 'Time per iteration'
ylabel 'Time [s]'
xlabel 'Iteration number[-]';
legend({sprintf('for          : %5.2fs',s.OverallFor),...
        sprintf('parfor       : %5.2fs',s.OverallParfor),...
        sprintf('parfor_sliced: %5.2fs',s.OverallParforSliced)},...
        'interpreter', 'none','fontname','courier')
Run Code Online (Sandbox Code Playgroud)

你可以cache2cell()在我的github回购中找到.

对切​​片数据很简单

您可能想知道如果我们for在切片数据上运行简单会发生什么?对于这个简单的玩具示例,如果我们通过切片数据来取消索引操作,我们就会删除代码的唯一瓶颈,并且for实际上比实际上更快parfor.

在此输入图像描述

然而,这是一个玩具示例,其中内循环的成本完全由索引操作获得.因此,为了parfor值得,内环应该更复杂和/或展开.

用切片的parfor保存内存

现在,假设您的内部循环更复杂并且简单的for循环更慢,让我们看看我们通过避免4个工作人员和50万行数据集(RAM中约760 MB)的广播数据来节省多少内存.

在此输入图像描述

如您所见,向工作人员发送了近3 GB的额外内存.切片操作需要一些内存来完成,但仍然比广播操作少得多,并且原则上可以覆盖初始数据集,因此一旦完成就可以承受可忽略的RAM成本.最后,parfor切片数据仅使用一小部分内存,即与正在使用的切片相对应的量.

切成细胞

原始数据按组切片,每个部分存储在一个单元格中.由于单元数组是一个引用数组,我们基本上将连续data的内存分区为独立的块.

虽然我们的样本data看起来像这样

data(1:5,:)
ans =
          620     -0.10696
          586      -1.1771
          625       2.2021
          858      0.86064
           78       1.7456
Run Code Online (Sandbox Code Playgroud)

切出来的c样子

c(1:5)
ans = 
    [ 969x2 double]
    [ 970x2 double]
    [ 949x2 double]
    [ 986x2 double]
    [1013x2 double]
Run Code Online (Sandbox Code Playgroud)

这里c{1}

c{1}(1:5,:)
ans =
            1      0.58205
            1      0.80183
            1     -0.73783
            1      0.79723
            1       1.0414
Run Code Online (Sandbox Code Playgroud)