【问题标题】:Memory and excecution speed in MatlabMatlab中的内存和执行速度
【发布时间】:2013-04-01 22:08:48
【问题描述】:

我正在尝试创建随机线条并选择其中一些,这非常罕见。我的代码相当简单,但要获得我可以使用的东西,我需要创建非常大的向量(即:

我的代码是

%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));

结果图是

【问题讨论】:

    标签: performance matlab memory-management


    【解决方案1】:

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

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

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

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

    如果您了解一些有关其在 Matlab 中的局限性的信息,则可以极大地加速循环。在旧版本的 Matlab 中,应该完全避免循环以支持“矢量化”代码,这是事实。在最近的版本中(我相信是 R2008a 及更高版本),Mathworks 引入了一种称为 JIT 加速器(即时编译器)的技术,它在执行期间将 M 代码即时翻译成 机器 语言.简而言之,JIT 加速器允许您的代码绕过 Matlab 的解释器并更直接地与底层硬件对话,这可以节省大量时间。

    你会听到很多关于在 Matlab 中应该避免循环的建议,现在不再是一般正确的了。尽管矢量化仍然有其价值,但仅使用矢量化代码实现的任何相当复杂的过程通常都难以辨认、难以理解、难以更改和难以维护。使用循环的相同过程的实现通常没有这些缺点,而且它通常会更快并且需要更少的内存

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

    这样的事情之一是plot;通常最好让循环什么都不做,除了收集和操作数据,并将任何绘图命令等延迟到循环之后。

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

    现在,如果您想知道,最后一步可能会产生巨大的不同——我知道有一种情况是将函数体复制粘贴到上层循环带来了 1200 倍的性能提升。 的执行时间已减少到分钟!)。

    实际上,您的循环中还有另一个次要 问题(它非常小,而且相当不方便,我会立即同意)—— 循环变量的名称不应该是ii这个名字是Matlab中虚数单位的名字,名字解析也会在每次迭代中不必要地消耗时间。它很小,但不容忽视。

    现在,考虑到这一切,我得出了以下实现:

    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. 
    

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

    分析器现在还显示了代码中工作量的均匀分布;没有因为内存使用或性能而真正脱颖而出的行。

    这可能不是最快的实现(如果有人看到明显的改进,请随时编辑它们)。但是,如果你愿意等待,你将能够做到MuonTracks(1e23)(或更高:)

    我还用C做了一个实现,可以编译成Matlab MEX文件:

    /* 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);
            }
        }
    }
    

    在 Matlab 中编译

    mex DoMuonCounting.c
    

    (在运行mex setup :) 之后 然后将它与像这样的小型 M-wrapper 一起使用:

    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
    

    这让我可以做

    >> tic, MuonTrack2(1e8); toc
    Elapsed time is 14.496355 seconds. 
    

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

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

    【讨论】:

    • 哇!非常感谢您的回答。这是非常有启发性的。不过,我有几点要讨论。首先第 55 行 (Y = bsxfun(@times, [15; 0], ones(size(X)));) 肯定有错误。我用Y = bsxfun(@times, 15, ones(size(X))); 替换了它,它似乎正在工作。其次,虽然它能够运行许多事件,但它比我的要慢得多,但我可以忍受。最后,出来的情节是y=15中的一条直线,不应该是这样的。似乎缺少端点。除此之外,您真的是 MatLab 专家!
    • 知道MuonTracks(1e29) 需要多少钱吗?
    • @Thanos:嘿,我只是人类;我很可能在某处犯了错误:) 当您发现错误时,请随时编辑/建议编辑。顺便说一句,我第一次运行你的函数时也得到了直线;这不正确?
    • @Thanos:关于速度;如果您有 Simulink/Fixed point 工具箱,我编写的函数非常适合嵌入式 Matlab (emlmex) 编译。只需在函数声明后直接添加%#eml 指令,并花一些时间编译它:) 但是可以肯定的是,一旦你设法做到这一点,mex 函数会快很多。
    • @Thanos:我的印象是X 是一个 2xN 矩阵。命令Y = bsxfun(@times, 15, ones(size(X)));Y = 15*ones(size(X)); 基本相同,但我认为您需要类似Y = 15 15 15 15 ...; 0 0 0 0 ...]; 的东西。哦,好吧,“需要一些调试”:)
    【解决方案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) &lt; width/2 会给你一个布尔值的列向量。请注意,我使用了&amp; 而不是&amp;&amp; - 这是因为&amp; 执行element-wise 逻辑AND,不像&amp;&amp; 只适用于标量值。通过这种方式,您可以构建整个表达式,以便变量 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
    

    【讨论】:

    • 为什么这被否决了?我正要发布一个几乎相同的建议
    • -1:@Dan:因为答案从完全过时的建议开始,默认情况下避免循环,并且根本没有提到 Matlab 分析器和性能优化的最佳实践。我会尽快发布答案。
    • 非常感谢您的回答。我确实将 if 条件替换为您建议的条件,但出现错误 ??? Error: File: test.m Line: 18 Column: 18 The expression to the left of the equals sign is not a valid target for an assignment.
    • @Thanos 你能发布代码吗?您不应该替换 if 语句条件,if 语句应该完全消失。抱歉,如果我没有说清楚。
    • @RodyOldenhuis 很公平,我有兴趣阅读您的答案。但是,downvote 似乎有些苛刻,因为它们应该表示“一个非常草率、不费吹灰之力的帖子,或者一个明显且可能危险地不正确的答案”——我的解决方案确实回答了这个问题,即使它没有不要像你希望的那样做得很好。通常的做法是发布您自己的答案,看看其他人是否同意您的答案更好。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-01-12
    • 1970-01-01
    • 2014-07-21
    • 2021-08-19
    • 1970-01-01
    相关资源
    最近更新 更多