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

Huge broadcast variable, optimizing code without parfor?

我有一个 40000 x 80000 的矩阵,我从中获得 "clusters" 的数量(具有相同值且彼此相邻的元素组),然后计算每个元素的大小集群。这是代码块。

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 个内核仍然会花费很多时间。

干杯!


这是一张图片,显示了使用我在问题中发布的代码与使用 @bla 建议的 bwconncomp 时图像大小的函数所花费的时间:

而不是 bwlabeln 使用内置函数 bwconncomp,例如:

...
s=bwconncomp(BWClass);
fragmentsize=sum(cellfun(@numel,s.PixelIdxList));
....

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

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

从代码中可以看出,相同class 的片段不相碰。但不清楚来自不同 classes 的片段是否不接触(即,如果它们接触,代码会产生相同的输出)。 假设它们不存在,则可以避免这两个循环。

想法是一次性标记所有片段,而不考虑 class。因此,不是每个 class 调用一次 bwlabeln,而是只调用一次。我不知道有多少 class,但是 这可能会大大减少计算时间。

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

从现在开始,我们只处理 regionprops 的输出,它可能包含(数量级)一百万个元素,数量微不足道(比像素数少 3 个数量级) .

这可能是代码:

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 找到新 class 开始的索引来改进它是微不足道的.

可以使用@bla bwconncomp 的建议重写上面的代码。此函数 returns 包含一个元胞数组的结构,该元胞数组具有每个标签的所有像素的索引。然后就没有必要使用 regionprops,可以直接找到大小(如@bla 所示)并使用每个标签的第一个索引找到 class(通过索引到 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));

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

NumberFragments = max(L(:));