【问题标题】:Turning a binary matrix into a vector of the last nonzero index in a fast, vectorized fashion以快速、向量化的方式将二进制矩阵转换为最后一个非零索引的向量
【发布时间】:2010-10-24 07:43:05
【问题描述】:

假设在 MATLAB 中,我有一个矩阵 A,其元素为 0 或 1。

如何以更快的矢量化方式获取每列的最后一个非零元素的索引向量?

我可以的

[B, I] = max(cumsum(A));

并使用I,但有更快的方法吗? (我假设 cumsum 即使将 0 和 1 相加也会花费一些时间)。

编辑:我想我的矢量化速度甚至超出了我的需要 - Mr.Fooz 的循环很棒,但 MATLAB 中的每个循环似乎都花费了 很多调试时间,即使它很快。

【问题讨论】:

    标签: matlab matrix vector vectorization


    【解决方案1】:

    快速是您应该担心的,不一定是完全矢量化。 Matlab 的最新版本在有效处理循环方面更加更加智能。如果有一种紧凑的矢量化方式来表达某些东西,它通常会更快,但不应该(总是)像过去那样害怕循环。

    clc
    
    A = rand(5000)>0.5;
    A(1,find(sum(A,1)==0)) = 1; % make sure there is at least one match
    
    % Slow because it is doing too much work
    tic;[B,I1]=max(cumsum(A));toc
    
    % Fast because FIND is fast and it runs the inner loop
    tic;
    I3=zeros(1,5000);
    for i=1:5000
      I3(i) = find(A(:,i),1,'last');
    end
    toc;
    assert(all(I1==I3));
    
    % Even faster because the JIT in Matlab is smart enough now
    tic;
    I2=zeros(1,5000);
    for i=1:5000
      I2(i) = 0;
      for j=5000:-1:1
        if A(j,i)
          I2(i) = j;
          break;
        end
      end
    end
    toc;
    assert(all(I1==I2));
    

    在 R2008a、Windows、x64 上,cumsum 版本需要 0.9 秒。循环和查找版本需要 0.02 秒。双循环版本仅需 0.001 秒。

    编辑:哪个最快取决于实际数据。当您将 0.5 更改为 0.999 时,双循环需要 0.05 秒(因为平均而言,它需要更长的时间来中断)。 cumsum 和 loop&find 实现的速度更加一致。

    编辑 2: gnovice 的 Flipud 解决方案很聪明。不幸的是,在我的测试机器上它需要 0.1 秒,所以它比 cumsum 快得多,但比循环版本慢。

    【讨论】:

    • 哇,你的循环的质量是否使它更快,或者任何这样的循环是执行任何类似操作的最快方法?
    • 当我开始编写示例时,我预计双循环最慢,而 loop&find 最快。当内部循环必须运行完成时,它有点慢(见编辑 2)。如今,Matlab 对每个函数都进行了即时编译。这使得循环更快(但对喜欢使用 EVAL 的人有一些意想不到的后果)。一般来说,如果您可以在不做额外工作的情况下使用矢量化(flipud 和 cumsum 解决方案已矢量化但会做额外工作),则使用矢量化仍然更好。
    • 另一个值得注意的有趣的事情是,在许多情况下,最新版本的 Matlab 在表达式解析方面很聪明。 E=A.*B.*C.*D;将在没有额外临时变量的情况下逐个元素地执行,类似于在 C 中手动编写操作时的执行方式。启用多核支持后,Matlab 会尝试查找操作的不相交部分并将它们分流到不同的部分CPU 内核。我不知道在调用之间划分独立循环迭代是否足够聪明。对于我所做的测试,我使用了 Core 2 Duo proc。
    • 在多核机器上,您甚至可以为您的外循环合并一个PARFOR(并行for循环),因为内循环在每一列上独立运行。
    • 按照 gnovice 的建议,我尝试了 parfor。第一次花了将近一秒钟(可能是因为它必须加载 parfor 逻辑和/或创建工作线程池)。几次运行后,它下降到 0.04 秒。因此,至少在像这样的简单循环中,仅在双核处理器上进行 5000 次迭代,串行版本要快得多。
    【解决方案2】:

    Mr Fooz 所示,使用新版本的 MATLAB,for 循环现在可以非常快。但是,如果您真的想拥有紧凑的矢量化代码,我建议您尝试一下:

    [B,I] = max(flipud(A));
    I = size(A,1)-I+1;
    

    这比您基于 CUMSUM 的答案要快,但仍不如 Fooz 先生的循环选项快。

    另外两件需要考虑的事情:

    • 对于其中没有任何内容的列,您希望获得什么结果?通过上面我给你的选项,我相信在这种情况下你会得到一个size(A,1)的索引(即A中的行数)。对于你的选择,我相信在这种情况下你会得到 1,而 Fooz 先生的嵌套循环选项会给你 0。

    • 这些不同选项的相对速度可能会根据 A 的大小和您期望它具有的非零数的数量而有所不同。

    【讨论】:

    • 聪明的主意。不幸的是,它比 loop&find 慢了大约 5 倍。
    • 这有点像我预期的结果:比 CUMSUM 快,但仍然比循环慢……尽管它仍然取决于 A 的大小和填充分数(OP 并没有真正定义) .
    猜你喜欢
    • 2019-05-02
    • 2021-01-02
    • 1970-01-01
    • 1970-01-01
    • 2019-01-15
    • 1970-01-01
    • 1970-01-01
    • 2018-12-12
    相关资源
    最近更新 更多