巨大的广播变量,优化代码没有parfor?

时间:2018-05-04 19:22:25

标签: matlab matrix memory optimization parfor

我有一个40000乘80000矩阵,从中我可以获得"簇的数量" (具有相同值的元素组彼此相邻),然后计算每个簇的大小。这是代码的一部分。

FRAGMENTSIZESCLASS = struct([]);  %We store the data in a structure
for class=1:NumberOfClasses
  %-First we create a binary image for each class-%
  BWclass = foto==class;
  %-Second we calculate the number of connected components (fragments)-%
  L = bwlabeln(BWclass);          %returns a label matrix, L, containing labels for the connected components in BWclass
  clear BWclass
  NumberFragments=max(max(L));
  %-Third we calculate the size of each fragment-%
  FragmentSize=zeros(NumberFragments,1);
  for f=1:NumberFragments      % potential improvement: using parfor while saring the memory between workers
    FragmentSize(f,1) = sum(L(:) == f);
  end
  FRAGMENTSIZESCLASS{class}=FragmentSize;
  clear L
end

问题是矩阵L是如此之大,以至于如果我使用parfor循环它变成一个广播变量然后内存会成倍增加而我的内存耗尽。

有关如何解决此问题的任何想法?我已经看过这个文件:https://ch.mathworks.com/matlabcentral/fileexchange/28572-sharedmatrix但不是一个简单的解决方案,即使我有24个内核仍然会花费很多时间。

干杯!

这是一张图片,显示了使用我在问题中发布的代码与使用bbconncomp建议的代码所占用的时间,如@bla所示: enter image description here

2 个答案:

答案 0 :(得分:2)

而不是CREATE TABLE YOURTABLE (ID INT); INSERT INTO YOURTABLE VALUES (10),(20),(30),(40),(50),(60); CREATE TABLE TEMP (ID INT); INSERT INTO TEMP VALUES (10),(20),(80),(90),(100),(110); INSERT INTO YOURTABLE SELECT T.* FROM YOURTABLE Y RIGHT OUTER JOIN TEMP T ON T.ID = Y.ID WHERE Y.ID IS NULL DELETE FROM TEMP; 使用内置函数bwlabeln,例如:

bwconncomp

答案 1 :(得分:0)

请注意,内存不足的原因可能是因为您使用parfor替换代码中的两个循环之一。在这两种情况下,每个工作线程都会在处理期间创建与foto大小相同的数组。请注意,在内部循环中,sum(L(:) == f)创建一个大小为L的逻辑数组,然后将其值相加(我不认为JIT足够聪明以优化该中间数组) )。

简而言之,以您的方式将操作并行化到如此大的图像上是不可行的。并行化的正确方法是将图像切割成图块,并在不同的线程上处理每个图块。如果片段很小(我敢于给出名称的假设),应该可以仅使用小的重叠来处理切片(切片需要重叠,以便每个碎片完全在内部至少在切片上)。在这种情况下删除重复项有点复杂,因此并行化并非易事。 但是,我希望以下建议不需要对代码进行并行化。

从代码中可以清楚地看到,同一类的片段不会触及。但是不清楚来自不同类的片段是否不接触(即如果它们的话,代码将产生相同的输出)。 假设它们不,可以避免两个循环。

这个想法是一次性标记所有片段,不论其类别如何。因此,不是每个类调用bwlabeln一次,而是只调用一次。我不知道有多少课程,但这可能会大大减少计算时间。

接下来,使用regionprops为每个片段确定其大小和类别。原则上,该操作也可以通过仅在图像上迭代一次来执行。请注意,您的代码FragmentSize(f,1) = sum(L(:) == f)会在每个片段上迭代一次图像。鉴于图像的大小,可能有数百万个碎片。 可以减少6个数量级的时间。

从这一点开始,我们只处理regionprops的输出,它可能包含(数量级)一百万个元素,一个微不足道的数量(比像素数少三个数量级)。 / p>

这可能是代码:

L = bwlabeln(foto>0);
cls  = regionprops(L,foto,'MinIntensity','Area');
clear L
sz = [cls.Area];
cls = [cls.MinIntensity];
NumberOfClasses = max(cls);
FRAGMENTSIZESCLASS = cell(NumberOfClasses,1);
for ii=1:NumberOfClasses
   FRAGMENTSIZESCLASS{ii} = sz(cls==ii);
end

这最后一个循环可能没有必要,我没有找到快速替代方案。我无法想象它的价格昂贵,但如果是这样的话,将它并行化是很容易的,或者通过排序cls并使用diff来找到它来改进它新班级开始的指数。

可以使用@ bla的bwconncomp建议重写上述代码。此函数返回一个包含单元数组的结构,该数组具有每个标签的所有像素的索引。然后没有必要使用regionprops,可以直接找到大小(如@bla所示)并使用每个标签的第一个索引来查找类(通过索引到foto):

cc = bwconncomp(foto>0);
sz = cellfun(@numel,cc.PixelIdxList);
cls = cellfun(@(indx)foto(indx(1)),cc.PixelIdxList);
NumberOfClasses = max(cls);
FRAGMENTSIZESCLASS2 = cell(NumberOfClasses,1);
for ii=1:NumberOfClasses
   FRAGMENTSIZESCLASS2{ii} = sz(cls==ii);
end

对于具有63个片段的256x256的小测试图像,这快3到4倍。但是,考虑到你正在处理的图像的大小,我担心这可能实际上是非常低效的。 要知道的唯一方法是尝试两种方法并计算时间!

关于您的代码的一些注意事项:

FRAGMENTSIZESCLASS = struct([]);

您将其初始化为空结构数组,但随后使用{}对其进行索引,将其转换为单元数组。如上所述,预分配数组总是好的:

FRAGMENTSIZESCLASS = cell(NumberOfClasses,1);
NumberFragments=max(max(L));

这会在水平轴(80k元素)上创建L的最大投影,然后在其中找到最大值。像在其他地方那样重塑矩阵更有效:

NumberFragments = max(L(:));