首页 > 解决方案 > 复制/索引 3D 矩阵可变部分的最快方法

问题描述

我有大量 3D 数据,其中包含在 2D 空间中获取的 1D 信号。处理这些数据的第一步是对所有信号进行阈值处理,以找到一个高振幅脉冲的到达。该脉冲存在于所有信号中,并且在不同时间到达。阈值处理后,应重新排序 3D 数据集,以便每个信号在脉冲到达时开始,而之前的信号被丢弃(信号的结尾并不重要,因为现在我将零连接到所有的结尾信号,因此数据保持相同的大小)。

现在,我通过以下方式实现了这一点:

首先,我首先计算所有信号中第一个超过阈值的样本的样本数

M = randn(1000,500,500); % example matrix of realistic size
threshold = 0.25*max(M(:,1,1)); % 25% of the maximum in the first signal as threshold
[~,index] = max(M>threshold); % indices of first sample exceeding threshold in all signals

接下来,我希望对所有信号进行移位,以便它们都以脉冲开始。现在,我已经以这种方式实现了它:

outM = zeros(size(M));     % preallocation for speed           
for i = 1:size(M,2)
    for j = 1:size(M,3)
        outM(1:size(M,1)+1-index(1,i,j),i,j) = M(index(1,i,j):end,i,j);  
    end
end

这很好用,而且我知道 for 循环不再那么慢了,但是对于我机器上的数据集来说,这很容易花费几秒钟。for 循环的单次迭代大约需要 0.05-0.1 秒,这对我来说似乎很慢,因为我只是复制了一个包含 500-2000 个双精度值的向量。

因此,我已经研究了解决这个问题的最佳方法,但目前我还没有找到更好的方法。我尝试了几件事:3D 蒙版、线性索引和并行循环 (parfor)。

对于 3D 蒙版,我检查了是否有任何改进的可能。因此,我首先构造一个逻辑掩码,然后将逻辑掩码索引/复制的速度与双嵌套 for 循环进行比较。

%% set up for logical mask copying
AA = logical(ones(500,1));  % only copy the first 500 values after the threshold value
Mask = logical(zeros(size(M)));
Jepla = zeros(500,size(M,2),size(M,3));
for i = 1:size(M,2)
    for j = 1:size(M,3)
        Mask(index(1,i,j):index(1,i,j)+499,i,j) = AA;
    end
end

%% speed comparison
tic
Jepla = M(Mask);
toc

tic
for i = 1:size(M,2)
    for j = 1:size(M,3)
        outM(1:size(M,1)+1-index(1,i,j),i,j) = M(index(1,i,j):end,i,j);  
    end
end
toc

for 循环每次都更快,即使复制的内容更多。

接下来,线性索引。

%% setup for linear index copying

%put all indices in 1 long column
LongIndex = reshape(index,numel(index),1);
% convert to linear indices and store in new variable
linearIndices = sub2ind(size(M),LongIndex,repmat(1:size(M,2),1,size(M,3))',repelem(1:size(M,3),size(M,2))');

% extend linear indices with those of all values to copy
k = zeros(numel(M),1);
count = 1;
for i = 1:numel(LongIndex)
    values = linearIndices(i):size(M,1)*i;
    k(count:count+length(values)-1) = values;
    count = count + length(values);
end
k = k(1:count-1);

% get linear indices of locations in new matrix
l = zeros(length(k),1);
count = 1;
for i = 1:numel(LongIndex) 
    values = repelem(LongIndex(i)-1,size(M,1)-LongIndex(i)+1);
    l(count:count+length(values)-1) = values;
    count = count + length(values);
end
l = k-l;

% create new matrix
outM = zeros(size(M));

%% speed comparison
tic
outM(l) = M(k);
toc

tic
for i = 1:size(M,2)
    for j = 1:size(M,3)
        outM(1:size(M,1)+1-index(1,i,j),i,j) = M(index(1,i,j):end,i,j);  
    end
end
toc

同样,另一种方法,线性索引,(很多)慢。失败后,我了解了并行化,尽管这肯定会加快我的代码速度。通过阅读 parfor 周围的一些文档并尝试一下,我将代码更改为以下内容:

gcp;
outM = zeros(size(M));      
inM = mat2cell(M,size(M,1),ones(size(M,2),1),size(M,3));
tic
parfor i = 1:500
    for j = 1:500
        outM(:,i,j) = [inM{i}(index(1,i,j):end,1,j);zeros(index(1,i,j)-1,1)];  
    end
end
end
toc

我改变了它,以便“outM”和“inM”都是切片变量,因为我读到这是最好的。这仍然很慢,比原来的 for 循环慢很多。

那么现在的问题是,我应该放弃尝试提高此操作的速度吗?还是有另一种方法可以做到这一点?我已经搜索了很多,现在不知道如何加快速度。很抱歉这个问题很长,但我想展示我尝试过的东西。先感谢您!

标签: matlabperformance

解决方案


不确定您的情况是否有一个选项,但看起来单元数组实际上在这里更快:

outM2 = cell(size(M,2),size(M,3)); 
tic; 
for i = 1:size(M,2)
  for j = 1:size(M,3)
    outM2{i,j} = M(index(1,i,j):end,i,j);  
  end
end
toc

第二个想法也更快出来,批处理所有必须移动相同值的数据:

tic;
for i = 1:unique(index).'
    outM(1:size(M,1)+1-i,index==i) = M(i:end,index==i);
end
toc

如果这种方法实际上更快,这完全取决于您的数据。 是的,整数值和逻辑索引可以混合使用


推荐阅读