在MATLAB中向量化/优化此代码?

时间:2013-05-07 08:23:04

标签: performance matlab image-processing vectorization

我正在构建我的第一个大型MATLAB程序,并且我已经设法为所有内容编写原始的矢量化代码,直到我尝试在立体投影中创建表示矢量密度的图像。在几次尝试失败之后,我去了Mathworks文件交换站点并找到了一个符合Malcolm Mclean礼貌的开源程序。使用测试矩阵,他的函数产生如下:

Vector density in Stereographic Projection

虽然这几乎就是我想要的,但他的代码依赖于三重嵌套的for循环。在我的工作站上,这段代码中的大小为25000x2的测试数据矩阵耗时65秒。这是不可接受的,因为我将在我的项目中扩展到大小为500000x2的数据矩阵。

到目前为止,我已经能够对最里面的循环进行矢量化(这是最长/最差的循环),但我想继续并尽可能完全摆脱循环。这是Malcolm的原始代码,我需要进行矢量化:

dmap = zeros(height, width); % height, width: scalar with default value = 32
for ii = 0: height - 1          % 32 iterations of this loop
    yi = limits(3) + ii * deltay + deltay/2; % limits(3) & deltay: scalars
    for jj = 0 : width - 1      % 32 iterations of this loop
        xi = limits(1) + jj * deltax + deltax/2; % limits(1) & deltax: scalars
        dd = 0;
        for kk = 1: length(x)   % up to 500,000 iterations in this loop
            dist2 = (x(kk) - xi)^2 + (y(kk) - yi)^2;
            dd = dd + 1 / ( dist2 + fudge); % fudge is a scalar
        end
        dmap(ii+1,jj+1) = dd;
    end
end

这就是我已经对最里面的循环做出的改变(这是对效率的最大消耗)。对于相同的测试矩阵,这会在我的机器上将时间从65秒减少到12秒,这比我想要的更好但速度更慢。

     dmap = zeros(height, width);
    for ii = 0: height - 1
        yi = limits(3) + ii * deltay + deltay/2;
        for jj = 0 : width - 1
            xi = limits(1) + jj * deltax + deltax/2;
            dist2 = (x - xi) .^ 2 + (y - yi) .^ 2;
            dmap(ii + 1, jj + 1) = sum(1 ./ (dist2 + fudge));
        end
    end
所以我的主要问题是,我可以对优化此代码进行进一步的更改吗?或者甚至是另一种解决问题的方法?我已经考虑过在程序的这一部分使用C ++或F#而不是MATLAB,如果我无法使用MATLAB代码达到合理的效率水平,我可能会这样做。

请注意,此时我没有任何其他工具箱,如果我这样做,那么我知道这将是微不足道的(例如,使用统计工具箱中的hist3)。

3 个答案:

答案 0 :(得分:8)

内存消耗解决方案

yi = limits(3) + deltay * ( 1:height ) - .5 * deltay;
xi = limits(1) + deltax * ( 1:width  ) - .5 * deltax;
dx = bsxfun( @minus, x(:), xi ) .^ 2;
dy = bsxfun( @minus, y(:), yi ) .^ 2;
dist2 = bsxfun( @plus, permute( dy, [2 3 1] ), permute( dx, [3 2 1] ) );
dmap = sum( 1./(dist2 + fudge ) , 3 );

修改

通过将操作分解为块来处理极大的xy

blockSize = 50000; % process up to XX elements at once
dmap = 0;
yi = limits(3) + deltay * ( 1:height ) - .5 * deltay;
xi = limits(1) + deltax * ( 1:width  ) - .5 * deltax;
bi = 1;
while bi <= numel(x)
    % take a block of x and y
    bx = x( bi:min(end, bi + blockSize - 1) );
    by = y( bi:min(end, bi + blockSize - 1) );
    dx = bsxfun( @minus, bx(:), xi ) .^ 2;
    dy = bsxfun( @minus, by(:), yi ) .^ 2;
    dist2 = bsxfun( @plus, permute( dy, [2 3 1] ), permute( dx, [3 2 1] ) );
    dmap = dmap + sum( 1./(dist2 + fudge ) , 3 );
    bi = bi + blockSize;
end     

答案 1 :(得分:2)

这是从1开始循环的原因的一个很好的例子。 iijj在0处启动的唯一原因是要删除ii * deltayjj * deltax条款,但会在dmap索引中引入顺序性,防止并行化

现在,通过重写循环,您可以在打开parfor()之后使用matlabpool

dmap = zeros(height, width);
yi   = limits(3) + deltay*(1:height) - .5*deltay;
matlabpool 8
parfor ii = 1: height
    for jj = 1: width
        xi    = limits(1) + (jj-1) * deltax + deltax/2;
        dist2 = (x - xi) .^ 2 + (y - yi(ii)) .^ 2;
        dmap(ii, jj) = sum(1 ./ (dist2 + fudge));
    end
end
matlabpool close

请记住,打开和关闭游泳池会产生很大的开销(我的英特尔酷睿双核处理器T9300上有10秒,远程游戏可以通过Matlab 32 Matlab进行测试)。

PS。我不确定内循环而不是外循环是否可以有意义地并行化。您可以尝试将parfor切换到内部并比较速度(我建议立即使用大矩阵,因为您已经在12秒内运行并且开销几乎一样大。)

答案 2 :(得分:1)

或者,使用kernel density estimation techniques可以解决此问题。这是统计工具箱的一部分,或者this KDE implementation by Zdravko Botev(不需要工具箱)。

对于下面的示例代码,N = 500000时为0.3秒,N = 1000000时为0.7秒。

N = 500000;
data = [randn(N,2); rand(N,1)+3.5, randn(N,1);];  % 2 overlaid distrib
tic; [bandwidth,density,X,Y] = kde2d(data); toc;
imagesc(density);

Example distribution density output