Matlab并行计算工具箱,在parfor循环中动态分配工作



我在matlab中处理一个长时间运行的parfor循环。

parfor iter=1:1000
   chunk_of_work(iter);
end

每次运行通常有2-3个定时异常值。也就是说,每执行1000个工作块,就有2-3个工作块的时间大约是其余工作块的100倍。随着循环接近完成,评估异常值的工作者继续运行,而其他工作者则没有计算负载。

这与parfor循环静态分配工作是一致的。这与这里的并行计算工具箱文档形成了对比:

"工作分配是动态的迭代范围,只有在他们完成了当前迭代的处理,结果是甚至工作负载分布。"

有什么想法吗?

我认为你引用的文档对什么是静态的工作分配有很好的描述:每个工人"都被分配了一个固定的迭代范围"。对于4名工人,这意味着第一名工人被分配iter 1:250,第二名工人被指派iter 251:500,。。。或者第一个是1:4:100,第二个是2:4:1000,依此类推

你没有确切地说你观察到了什么,但你所描述的与动态工作负载分布非常一致:首先,四个(示例)工作人员每人在一个iter上工作,第一个完成的工作人员在第五个上工作,下一个完成的工作人员(如果前四个工作人员中的三个需要更长的时间,这可能是相同的)在第六个工作,以此类推。现在,如果你的异常值是数字20,850和900,按照MATLAB选择处理循环迭代的顺序,每个迭代的时间是循环迭代的100倍,这只意味着第21次到第320次迭代将由四个工人中的三个来解决,而其中一个工人正忙于第20次迭代(到320次将完成,现在假设非异常值计算时间的分布大致均匀)。然而,被分配到第850次迭代的工作者将继续运行,即使在另一个迭代解决了#1000,#900也是如此。事实上,如果大约有1100次迭代,那么在#900上工作的迭代应该大致在其他迭代完成的时候完成。

[编辑,因为最初的措辞暗示MATLAB仍然会按照从1到1000的顺序分配parfor循环的迭代,这是不应该假设的]

长话短说,除非你首先找到处理异常值的方法(当然,这需要你先验地知道哪些异常值是异常值,并找到一种方法让MATLAB用这些异常值启动parfor循环处理),否则仅靠动态工作负载分布无法避免你观察到的效果。

补充:然而,我认为,随着"循环接近完成,评估异常值的工作人员*s*继续运行"的观察似乎暗示了以下中的至少一个

  1. 异常值在某种程度上是MATLAB开始处理的最后一次迭代
  2. 您有许多工作者,按照迭代次数的数量级
  3. 您对异常值数量的估计(2-3)或对其计算时间惩罚的估计(因子100)过低

PARFOR中的工作分布在某种程度上是确定性的。通过让每个工作人员将事情的进展记录到磁盘上,您可以准确地观察到发生了什么,但基本上,事实证明,PARFOR以确定性的方式将您的循环划分为块,但会动态地将它们分配出去。不幸的是,目前没有办法控制这种分块。

然而,如果你无法预测1000个病例中的哪一个会是异常值,那么很难想象有一个有效的方案来分配工作。

如果你能预测你的异常值,你可能能够利用这样一个事实,粗略地说,PARFOR以相反的顺序执行循环迭代,所以你可以把它们放在循环的"末尾",这样工作就可以立即开始了。

@arne.b的回答很好地描述了您面临的问题,对此我没有任何补充。

但是,并行计算工具箱确实包含用于将作业分解为独立执行的任务的函数。从你的问题中,不可能得出这样的结论:这是合适的,也不可能得出不适合你的申请的结论。如果是这样的话,一般的策略是将作业分解成一定大小的任务,让每个处理器处理一个任务,完成后返回未完成任务的堆栈,开始另一个任务。

您可能能够分解您的问题,使一个任务取代一个循环迭代(大量任务,管理计算的大量开销,但最好的负载平衡),或者使一个工作取代N个循环迭代。作业和任务也比parfor更难实现。

作为PARFOR的替代方案,在R2013b及更高版本中,您可以使用PARFEVAL并以任何您认为合适的方式分配工作。如果合适的话,一旦得到足够的结果,你甚至可以取消"时间异常值"。当然,将现有循环划分为1000个单独的远程PARFEVAL调用会产生开销。也许这是个问题,也许不是。这是我想象中的事情:

for idx = 1:1000
     futures(idx) = parfeval(@chunk_of_work, 1, idx);
end
done = false; numComplete = 0;
timer = tic();
while ~done
    [idx, result] = fetchNext(futures, 10); % wait up to 10 seconds
    if ~isempty(idx)
        numComplete = numComplete + 1;
        % stash result
    end
    done = (numComplete == 1000) || (toc(timer) > 100);
end
% cancel outstanding work, has no effect on completed futures
cancel(futures);

最新更新