使用每次迭代的工作量的先验估计来预测并行循环的运行时间(对于给定的工作数)

时间:2013-11-07 18:53:11

标签: matlab parallel-processing load-balancing parfor

我正在研究自适应矩阵 - 向量乘法的MATLAB实现,用于非常大的稀疏矩阵,这些稀疏矩阵来自PDE的特定离散化(具有已知的稀疏结构)。

经过大量的预处理后,我最终得到了许多不同的块(大于200),我想为此计算所选的条目。

其中一个预处理步骤是确定我想要计算的每个块的(条数)条目,这使我几乎可以衡量每个块将采用的时间量(所有意图和目的都是正交的)每个条目的努力都是一样的。)

感谢https://stackoverflow.com/a/9938666/2965879,我能够通过以相反的顺序对块进行排序来利用这一点,从而将MATLAB首先从最大的块开始。

但是,条目数与块之间的差别很大,直接运行的parfor受到具有最大条目数的块的严重限制,即使它们被反向馈入循环。

我的解决方案是按顺序执行最大的块(但在条目级别上进行并行化!),只要每次迭代的开销不重要,就可以了。块不会太小。其余的块然后我用parfor做。理想情况下,我让MATLAB决定如何处理这个问题,但由于嵌套的parfor-loop失去了并行性,所以这不起作用。此外,将两个环组合成一个(几乎)是不可能的。

我现在的问题是如何最好地确定串行和并联机制之间的这种截止,同时考虑到我对条目数量的信息(有序条目的曲线形状可能因不同的问题而有所不同),以及我有的工人数量。

到目前为止,我一直在与标准PCT许可证下的12名工作人员合作,但由于我现在已经开始在集群上工作,因此确定这种截止变得越来越重要(因为对于许多核心而言与并行循环相比,串行循环的开销变得越来越昂贵,但同样地,使用阻塞其余部分的块甚至更昂贵。)

对于12个核心(分别是我正在使用的计算服务器的配置),我已经找到了每个工作人员100个条目的合理参数作为截止,但是当核心数量不合适时与块数相关的不再小(例如64 vs 200)。

我试图缩小具有不同功率的内核数量(例如1 / 2,3 / 4),但这也不能始终如一地工作。接下来,我尝试将块分组,并确定当条目大于每批次的平均值时的截止值。他们远离最后的批次数:

logical_sml = true(1,num_core); i = 0;
while all(logical_sml)
    i = i+1;
    m = mean(num_entr_asc(1:min(i*num_core,end))); % "asc" ~ ascending order 
    logical_sml = num_entr_asc(i*num_core+(1:num_core)) < i^(3/4)*m;  
        % if the small blocks were parallelised perfectly, i.e. all  
        % cores take the same time, the time would be proportional to  
        % i*m. To try to discount the different sizes (and imperfect  
        % parallelisation), we only scale with a power of i less than  
        % one to not end up with a few blocks which hold up the rest  
end  
num_block_big = num_block - (i+1)*num_core + sum(~logical_sml);

(注意:此代码不适用于长度不是num_entr_asc的倍数的向量num_core,但我决定省略min(...,end)构造的易读性。)

我还省略了< max(...,...)用于组合两个条件(即每个工人的最小条目),这是必要的,以便不太早发现截止。我也想过以某种方式使用方差,但到目前为止所有的尝试都不尽如人意。

如果有人对如何解决这个问题有个好主意,我将非常感激 感谢您阅读这个很长的问题,
最好的问候,
阿克塞尔

聚苯乙烯。由于我的“亲爱的堆栈溢出”似乎已经过滤了,所以我要多次感谢我已经找到了我的问题的解决方案。

1 个答案:

答案 0 :(得分:0)

我提出了一个令人满意的解决方案,所以如果有人感兴趣,我想我会分享它。我仍然会对如何改进/微调方法表示赞赏。

基本上,我认为唯一合理的方法是为并行循环构建调度程序的(非常)基本模型:

function c=est_cost_para(cost_blocks,cost_it,num_cores)
% Estimate cost of parallel computation

% Inputs:
%   cost_blocks: Estimate of cost per block in arbitrary units. For
%       consistency with the other code this must be in the reverse order
%       that the scheduler is fed, i.e. cost should be ascending!
%   cost_it:     Base cost of iteration (regardless of number of entries)
%       in the same units as cost_blocks.
%   num_cores:   Number of cores
%
% Output:
%   c: Estimated cost of parallel computation

num_blocks=numel(cost_blocks);
c=zeros(num_cores,1);

i=min(num_blocks,num_cores);
c(1:i)=cost_blocks(end-i+1:end)+cost_it;
while i<num_blocks
    i=i+1;
    [~,i_min]=min(c); % which core finished first; is fed with next block
    c(i_min)=c(i_min)+cost_blocks(end-i+1)+cost_it;
end

c=max(c);

end

空迭代的参数cost_it是许多不同副作用的粗略混合,可以想象它们是分开的:for / parfor中空迭代的成本 - 循环(每个块也可能不同),以及启动时间resp。传输parfor - 循环的数据(可能更多)。把所有东西放在一起的主要原因是我不想估计/确定更细粒度的成本。

我使用上述例程以下列方式确定截止值:

% function i=cutoff_ser_para(cost_blocks,cost_it,num_cores)
% Determine cut-off between serial an parallel regime

% Inputs:
%   cost_blocks: Estimate of cost per block in arbitrary units. For
%       consistency with the other code this must be in the reverse order
%       that the scheduler is fed, i.e. cost should be ascending!
%   cost_it:     Base cost of iteration (regardless of number of entries)
%       in the same units as cost_blocks.
%   num_cores:   Number of cores
%
% Output:
%   i: Number of blocks to be calculated serially

num_blocks=numel(cost_blocks);
cost=zeros(num_blocks+1,2);

for i=0:num_blocks
    cost(i+1,1)=sum(cost_blocks(end-i+1:end))/num_cores + i*cost_it;
    cost(i+1,2)=est_cost_para(cost_blocks(1:end-i),cost_it,num_cores);
end

[~,i]=min(sum(cost,2));
i=i-1;

end

特别是,我不会夸大/更改est_cost_para的值,该值假定(除cost_it之外)最乐观的调度。我把它留下来主要是因为我不知道什么是最好的。为了保守(即避免向并行环路馈送太大的块),当然可以添加一些百分比作为缓冲器或甚至使用电源&gt; 1来扩大并行成本。

另请注意,est_cost_para的连续减少块被调用(尽管我对两个例程使用变量名cost_blocks,一个是另一个的子集)。

与我在罗嗦的问题中的方法相比,我看到两个主要优点:

  1. 使用模拟调度程序捕获的数据(模块数量和成本)与核心数量之间相对复杂的依赖性比单个公式更好。
  2. 通过计算串行/并行分布的所有可能组合的成本然后取最小值,从一侧读取数据时不能过早地“卡住”(例如,通过相对于数据大的跳转到目前为止,但与总数相比较小。
  3. 当然,通过一直调用est_cost_para并使用while循环来渐进复杂度更高,但在我的情况下(num_blocks<500)这绝对可以忽略不计。

    最后,如果cost_it的正确值不容易出现,可以尝试通过测量每个块的实际执行时间以及它的纯粹并行部分来计算它,然后尝试将结果数据拟合到成本预测中,并为下一次例程调用获得cost_it的更新值(通过使用总成本和并行成本之间的差异,或者通过在拟合公式中插入零成本) 。对于有问题的问题,这应该有希望“收敛”到最有用的cost_it值。