Matlab中的内存和执行速度

时间:2013-04-10 07:28:24

标签: performance matlab memory-management

我正在尝试创建随机行并选择其中一些,这是非常罕见的。我的代码相当简单,但是为了获得我可以使用的东西,我需要创建非常大的向量(即:< 100000000 x 1,在我的代码中跟踪变量)。有没有办法能够创建更大的向量并减少所有这些计算所需的时间?

我的代码是

%Initial line values

tracks=input('Give me the number of muon tracks: ');
width=1e-4;
height=2e-4;

Ystart=15.*ones(tracks,1);
Xstart=-40+80.*rand(tracks,1);
%Xend=-40+80.*rand(tracks,1);
Xend=laprnd(tracks,1,Xstart,15);
X=[Xstart';Xend'];
Y=[Ystart';zeros(1,tracks)];
b=(Ystart.*Xend)./(Xend-Xstart);
hot=0;
cold=0;

for i=1:tracks
    if ((Xend(i,1)<width/2 && Xend(i,1)>-width/2)||(b(i,1)<height && b(i,1)>0))
        plot(X(:, i),Y(:, i),'r');%the chosen ones!
        hold all
        hot=hot+1;
    else
        %plot(X(:, i),Y(:, i),'b');%the rest of them
        %hold all
        cold=cold+1;
    end
end

我也在使用并调用拉普拉斯分布生成器制作我的Elvis Chen,可以找到here

function y  = laprnd(m, n, mu, sigma)
%LAPRND generate i.i.d. laplacian random number drawn from laplacian distribution
%   with mean mu and standard deviation sigma. 
%   mu      : mean
%   sigma   : standard deviation
%   [m, n]  : the dimension of y.
%   Default mu = 0, sigma = 1. 
%   For more information, refer to
%   http://en.wikipedia.org./wiki/Laplace_distribution

%   Author  : Elvis Chen (bee33@sjtu.edu.cn)
%   Date    : 01/19/07

%Check inputs
if nargin < 2
    error('At least two inputs are required');
end

if nargin == 2
    mu = 0; sigma = 1;
end

if nargin == 3
    sigma = 1;
end

% Generate Laplacian noise
u = rand(m, n)-0.5;
b = sigma / sqrt(2);
y = mu - b * sign(u).* log(1- 2* abs(u));

结果图是

2 个答案:

答案 0 :(得分:3)

正如您所指出的,您的问题是双重的。一方面,你有记忆问题,因为你需要做很多试验。另一方面,您遇到性能问题,因为您必须处理所有这些试验。

每个问题的解决方案通常会对另一个问题产生负面影响。恕我直言,最好的方法是找到妥协。

更多的试验只能让你摆脱矢量化所需的那些庞大的数组,并使用不同的策略来进行循环。我将优先考虑使用更多试验的可能性,可能以最佳性能为代价。

当我在Matlab profiler中按原样执行代码时,它立即显示所有变量的初始内存分配需要花费大量时间。它还显示plothold all命令是它们中最耗时的行。一些更多的反复试验表明,在trials错误开始出现之前,您可以执行OUT OF MEMORY的最大值令人失望。

如果您对Matlab中的局限性有所了解,那么循环可以大大加速。在旧版本的Matlab中,过去应该完全避免使用循环来支持“矢量化”代码。在最近的版本中(我相信R2008a及更高版本),Mathworks引入了一项称为JIT加速器(即时编译器)的技术,该技术在执行期间即时将M代码转换为机器语言。简而言之,JIT加速器允许您的代码绕过Matlab的解释器并更直接地与底层硬件交谈,这可以节省很多时间。

在Matlab中应该避免大量循环的建议,不再是通常为真。虽然矢量化仍然具有其价值,但是使用仅矢量化代码实现的任何相当复杂的过程通常都是难以理解的,难以理解,难以改变且难以维护。使用循环的相同过程的实现通常没有这些缺点,而且,它通常会更快并且需要更少的内存

不幸的是,JIT加速器有一些令人讨厌的(和恕我直言,不必要的)限制,你必须要了解。

其中一件事是plot;除了收集和操作数据之外,让循环做 nothing 通常更好的做法,并延迟任何绘图命令等,直到循环之后。

另一个问题是hold; hold函数是一个Matlab内置函数,意思是,它是用M语言实现的。当在循环中使用时,Matlab的JIT加速器无法加速非内置函数,这意味着,整个循环将以Matlab的解释速度运行,而不是机器语言速度!因此,也要将此命令延迟到之后循环:)

现在,如果你想知道,最后一步可以产生 巨大 的区别 - 我知道有一种情况是将函数体复制粘贴到上层循环导致性能提升1200倍。 的执行时间已缩短为分钟!)。

在你的循环中实际上存在另一个次要问题(这个问题非常小,相当不方便,我会立即同意) - 循环变量的名称不应该是{{1} }。名称i是Matlab中虚构单元的名称,名称解析也会不必要地消耗每次迭代的时间。它很小,但不可忽视。

现在,考虑到所有这些,我来看以下实现:

i

通过这种实现,我可以这样做:

function [hot, cold, h] = MuonTracks(tracks)

    % NOTE: no variables larger than 1x1 are initialized

    width  = 1e-4;
    height = 2e-4;

    % constant used for Laplacian noise distribution  
    bL = 15 / sqrt(2);

    % Loop through all tracks
    X = [];
    hot = 0;  
    ii = 0;          
    while ii <= tracks

        ii = ii + 1;

        % Note that I've inlined (== copy-pasted) the original laprnd()
        % function call. This was necessary to work around limitations 
        % in loops in Matlab, and prevent the nececessity of those HUGE 
        % variables. 
        %
        % Of course, you can still easily generalize all of this: 

        % the new data
        u = rand-0.5;

        Ystart = 15; 
        Xstart = 800*rand-400;
        Xend   = Xstart - bL*sign(u)*log(1-2*abs(u));

        b = (Ystart*Xend)/(Xend-Xstart);


        % the test
        if ((b < height && b > 0)) ||...
            (Xend < width/2 && Xend > -width/2)

            hot = hot+1;

            % growing an array is perfectly fine when the chances of it
            % happening are so slim
            X = [X [Xstart; Xend]]; %#ok

        end
    end

    % This is trivial to do here, and prevents an 'else' in the loop
    cold = tracks - hot;

    % Now plot the chosen ones
    h = figure;
    hold all
    Y = repmat([15;0], 1, size(X,2));
    plot(X, Y, 'r'); 

end

内存占用完全可以忽略不计。

现在,探查器还可以在代码中显示出良好且均匀的工作量;没有因为内存使用或性能而真正脱颖而出的行。

这可能不是最快的实现(如果有人看到明显的改进,请随时编辑它们)。但是,如果你愿意等待,你将能够>> tic, MuonTracks(1e8); toc Elapsed time is 24.738725 seconds. (或更高)

我还在C中完成了一个实现,可以编译成Matlab MEX文件:

MuonTracks(1e23)

使用

在Matlab中编译
/* DoMuonCounting.c */

#include <math.h>
#include <matrix.h>
#include <mex.h>
#include <time.h>
#include <stdlib.h>


void CountMuons(
    unsigned long long tracks,
    unsigned long long *hot, unsigned long long *cold, double *Xout);


/* simple little helper functions */
double sign(double x) { return (x>0)-(x<0); }
double rand_double()  { return (double)rand()/(double)RAND_MAX; }


/* the gateway function */
void mexFunction(int nlhs, mxArray *plhs[], int nrhs, const mxArray *prhs[])
{
    int
        dims[] = {1,1};

    const mxArray
        /* Output arguments */
        *hot_out  = plhs[0] = mxCreateNumericArray(2,dims, mxUINT64_CLASS,0),
        *cold_out = plhs[1] = mxCreateNumericArray(2,dims, mxUINT64_CLASS,0),
        *X_out    = plhs[2] = mxCreateDoubleMatrix(2,10000, mxREAL);

    const unsigned long long
        tracks = (const unsigned long long)mxGetPr(prhs[0])[0];

    unsigned long long
        *hot  = (unsigned long long*)mxGetPr(hot_out),
        *cold = (unsigned long long*)mxGetPr(cold_out);
    double
        *Xout = mxGetPr(X_out);

    /* call the actual function, and return */
    CountMuons(tracks, hot,cold, Xout);
}


// The actual muon counting
void CountMuons(
    unsigned long long tracks,
    unsigned long long *hot, unsigned long long *cold, double *Xout)
{
    const double
        width  = 1.0e-4,
        height = 2.0e-4,
        bL     = 15.0/sqrt(2.0),
        Ystart = 15.0;

    double
        Xstart,
        Xend,
        u,
        b;
    unsigned long long
        i = 0ul;


    *hot  = 0ul;
    *cold = tracks;

    /* seed the RNG */
    srand((unsigned)time(NULL));

    /* aaaand start! */
    while (i++ < tracks)
    {
        u = rand_double() - 0.5;

        Xstart = 800.0*rand_double() - 400.0;
        Xend   = Xstart - bL*sign(u)*log(1.0-2.0*fabs(u));

        b = (Ystart*Xend)/(Xend-Xstart);

        if ((b < height && b > 0.0) || (Xend < width/2.0 && Xend > -width/2.0))
        {
            Xout[0 + *hot*2] = Xstart;
            Xout[1 + *hot*2] = Xend;
            ++(*hot);
            --(*cold);
        }
    }
}

运行mex DoMuonCounting.c :)之后,然后将它与这样的小型M-wrapper一起使用:

mex setup

允许我这样做

function [hot,cold, h] = MuonTrack2(tracks)

    % call the MEX function 
    [hot,cold, Xtmp] = DoMuonCounting(tracks);

    % process outputs, and generate plots

    hot = uint32(hot); % circumvents limitations in 32-bit matlab

    X = Xtmp(:,1:hot);
    clear Xtmp

    h = NaN;
    if ~isempty(X)
        h = figure;
        hold all         
        Y = repmat([15;0], 1, hot);
        plot(X, Y, 'r');       
    end    
end

请注意,MEX版本的内存占用量略大,但我认为没什么好担心的。

我看到的唯一缺陷是Muon计数的固定最大数量(硬编码为10000,作为>> tic, MuonTrack2(1e8); toc Elapsed time is 14.496355 seconds. 的初始数组大小;需要因为标准C中没有动态增长的数组)...如果你担心这个限制可能被打破,只需增加它,将其改为等于Xout的一小部分,或做一些更聪明(但更痛苦)的动态数组增长技巧。

答案 1 :(得分:2)

在Matlab中,有时候更快地进行矢量化而不是使用for循环。例如,这个表达式:

(Xend(i,1) < width/2 && Xend(i,1) > -width/2) || (b(i,1) < height && b(i,1) > 0)

是为i的每个值定义的,可以用这样的矢量化方式重写:

isChosen = (Xend(:,1) < width/2 & Xend(:,1) > -width/2) | (b(:,1) < height & b(:,1)>0)

Xend(:,1)之类的操作会为您提供一个列向量,因此Xend(:,1) < width/2会为您提供一个布尔值的列向量。请注意,我使用的是&而不是&& - 这是因为&执行元素明智的逻辑AND,与&&不同适用于标量值。通过这种方式,您可以构建整个表达式,以便变量isChosen包含布尔值的列向量,对应Xend / b向量的每一行。

获取计数现在就像这样简单:

hot = sum(isChosen);

因为true1表示。和

cold = sum(~isChosen);

最后,您可以通过使用布尔向量来选择行来获取数据点:

plot(X(:, isChosen),Y(:, isChosen),'r');    % Plot chosen values
hold all;
plot(X(:, ~isChosen),Y(:, ~isChosen),'b');  % Plot unchosen values

编辑:代码应如下所示:

isChosen = (Xend(:,1) < width/2 & Xend(:,1) > -width/2) | (b(:,1) < height & b(:,1)>0);
hot = sum(isChosen);
cold = sum(~isChosen);
plot(X(:, isChosen),Y(:, isChosen),'r');    % Plot chosen values