具有大量零的矩阵的有效乘法

时间:2015-07-08 21:03:46

标签: matlab matrix

我有两个采用以下形式的数组:

    0…0…0 0 0 0…0        0…0…0 0 0 0…0
    ⋮  ⋮ ⋮  ⋮ ⋮  ⋮ ⋮        ⋮  ⋮ ⋮  ⋮ ⋮  ⋮ ⋮
    0…0 0 0 0 0…0        0…0 0 0 0 0…0
A = 0…0 1 2 3 0…0    B = 0…0 9 8 7 0…0
    0…0 4 5 6 0…0        0…0 6 5 4 0…0
    0…0 0 0 0 0…0        0…0 0 0 0 0…0
    ⋮  ⋮ ⋮  ⋮ ⋮  ⋮ ⋮        ⋮  ⋮ ⋮  ⋮ ⋮  ⋮ ⋮
    0…0…0 0 0 0…0        0…0…0 0 0 0…0

AB的非零区域的大小可能不完全相同,但上图已经变得有点笨拙。

最终,我所追求的价值是sum(sum(A .* B))。我觉得必须有一种方法只能将非零元素相乘,但我能想出的每一种方法似乎都会导致MATLAB复制矩阵,这完全破坏了通过减少操作数量而获得的任何收益。 B被重用于内循环的几次迭代,因此我可以通过许多循环迭代在B上分摊昂贵的计算。

到目前为止,我尝试了以下方法:

天真的方法:

function C = innerLoop(A, B)
    C = sum(sum(A .* B))
end
使用此功能,

innerLoop在86,000次通话中大约需要4.3秒。 (基于MATLAB的“运行和时间”功能。)

缩小B首先:

function B = resize(self, B1)
    rows = abs(sum(B, 2)) > 1e-4;
    top = find(rows, 1, 'first');
    bot = find(rows, 1, 'last');

    cols = abs(sum(B, 1)) > 1e-4;
    left = find(cols, 1, 'first');
    right = find(cols, 1, 'last');

    self.Rows = top:bot; % Store in class properties for use in inner loop
    self.Cols = left:right; % Store in class properties for use in inner loop
    B = B(top:bot, left:right);
end

function C = innerLoop(A, B)
    result = A(self.Rows, self.Cols) .* B;
    C = sum(sum(result));
end

我对这种方法的希望是MATLAB会意识到我没有写A并且没有写副本,但这种方法在innerLoop中花了大约6.8秒。

我还尝试仅计算innerLoop之外的偏移量,希望MATLAB能够理解我在两个矩阵上使用相同的下标来优化事物的事实:

function B = resize(self, B1)
    rows = abs(sum(B, 2)) > 1e-4;
    top = find(rows, 1, 'first');
    bot = find(rows, 1, 'last');

    cols = abs(sum(B, 1)) > 1e-4;
    left = find(cols, 1, 'first');
    right = find(cols, 1, 'last');

    self.Rows = top:bot; % Store in class properties for use in inner loop
    self.Cols = left:right; % Store in class properties for use in inner loop
end

function C = innerLoop(A, B)
    result = A(self.Rows, self.Cols) .* B(self.Rows, self.Cols);
    C = sum(sum(result));
end

不幸的是,这是最慢的约8.6秒。

我也尝试使用以下代码进行循环:

function C = innerLoop(A, B)
    C = 0;
    for i = self.Rows
        for j = self.Cols
            C = C + field(i, j) * self.Sensitivity.Z(i, j);
        end
    end
end

我知道在MATLAB中循环过去非常慢,但我读过一些文章表明它比以前快得多。也就是说,如果循环版本运行完毕,我会告诉你需要多长时间,但现在已经过了几分钟了。

有关如何优化此功能的任何建议都将非常感谢!

2 个答案:

答案 0 :(得分:6)

您可以使用稀疏矩阵来解决此问题。 Matlab自动处理不同大小的“非稀疏部分”。要获得稀疏矩阵,使用sparse - 函数。之后,您可以进行逐元素乘法,然后将C中的所有元素加到一个单独的行中。

A = [0 0 0 0 0 0 0;
     0 0 0 0 0 0 0;
     0 0 1 2 3 0 0;
     0 0 4 5 6 0 0;
     0 0 0 0 0 0 0;
     0 0 0 0 0 0 0];

B = [0 0 0 0 0 0 0;
     0 0 0 0 0 0 0;
     0 0 9 8 7 0 0;
     0 0 6 5 4 0 0;
     0 0 0 0 0 0 0;
     0 0 0 0 0 0 0];

A = sparse(A);
B = sparse(B);

C = A .* B;
sum(C(:))

答案 1 :(得分:3)

这是我的初始帖子的重写

我的立场得到了纠正。我不知道我以前的测试出了什么问题。我认为它可能是稀疏算法的32比64位实现,但不是偶数。在两台不同的机器上仔细重新运行基准测试后,sparse方法将获胜。

sumsparse

基准代码:

function ExecTimes = bench_sum_sparse

nOrder = (1:9).' * 10.^(2:3) ; nOrder = [nOrder(:) ; (1:2).'*1e4] ;
%// nOrder = (1:30)*100 ;
npt = numel(nOrder) ;

ExecTimes = zeros( npt , 3 ) ;

fprintf('\n%s%5d \n','Calculating for N = ',0) ;

for k = 1:npt

    % // sample data
    N = nOrder(k) ;
    fprintf('\b\b\b\b\b\b%5d\n',N) ; % // display progress
    A = zeros(N) ;
    B = A ;
    innerMat = (1:10).'*(1:10) ;        %'
    ixInnerMat = innerMat + N/2 - 5 ; 

    A(ixInnerMat) = innerMat ;
    B(ixInnerMat) = innerMat ;

    % // benchmark
    f1 = @() innerLoop(A,B) ;
    ExecTimes(k,1) = timeit( f1 ) ;
    clear f1

    f2 = @() sum_logicIndex(A, B) ;
    ExecTimes(k,2) = timeit( f2 ) ;
    clear f2

    A = sparse(A);
    B = sparse(B);
    f3 = @() sum_sparse(A,B) ;
    ExecTimes(k,3) = timeit( f3 ) ;
    clear f3

    %// checksum1 = f1() - f2 ()
    %// checksum2 = f1() - f3 ()

end

end

function C = innerLoop(A, B)
    C = sum(sum(A .* B)) ;
end

function C = sum_logicIndex(A,B)
    idx = A>0 & B>0 ;
    C = sum(sum(A(idx).*B(idx))) ;
end

function C = sum_sparse(A,B)
    C = A .* B;
    C = sum(C(:)) ;
end

所有测试都在Matlab 2013b上运行 64位机器:Intel I7-3820 @ 3.6GHz - 16 GB RAM - Windows 7
32位机器:Intel E2200 @ 2.2GHz - 3GB RAM - Windows 8.1

相关问题