【问题标题】:How does MATLAB handle dynamic array allocation?MATLAB 如何处理动态数组分配?
【发布时间】:2015-09-24 16:28:07
【问题描述】:

我并不精通 MATLAB,并且对它如何处理动态内存分配感到好奇在后台

一种主要的方法是分配大块并且超出必要的范围,这样您就不必为添加的每个新元素进行分配。在做一些研究时,我看到很多人亲自管理他们自己的大块分配(假设他们不知道他们的最终大小)或者做一些事情,比如创建一个最大大小,然后修剪。一个例子是Undocumented MATLAB,它建议您自己执行块内存分配。我会认为像 MATLAB 这样的语言会知道自己做这件事,我不需要关心这样的问题。这对我来说意味着,如果您尝试将单个新元素附加到数组中,MATLAB 只会为该单个元素分配新内存,这是非常低效的。

我的问题有两个

  • 对于动态数组,MATLAB 分配的内存是大块还是超出了节省计算效率所需的内存,还是只为连接的内容分配内存?
  • 如果是前者,MATLAB 选择采用这种设计是否有原因?

【问题讨论】:

  • 这是一个有趣的问题。期待答案。
  • @zephyr 看看我的编辑,也许会有所帮助?
  • 一个有趣的问题,但我怀疑你会得到很多答案。 MathWorks 通常不太愿意提供有关 MATLAB 内部的详细信息。
  • 确实如此,但我原以为有人会知道这一点。对于某些算法设计,这是重要且必要的知识。我想知道我是否可以信任 MATLAB 为我处理这个过程,或者我是否应该自己处理。
  • 票数接近的总是一些白痴。

标签: arrays matlab memory-management


【解决方案1】:

我记得几年前在 Matlab 博览会上,他们讨论了总部正在开发的东西 - 其中之一是自动预分配内存。

没有提到什么时候会发布,或者是否会发布......而且我从没听说过它......

根据我的经验 - 我一直自己管理动态分配 - 如果这部分代码出现问题(即数组在我认为它们不应该增长时......),我总是注意到严重的减速p>

所以我认为你需要自己管理它是公平的。

【讨论】:

    【解决方案2】:

    MATLAB 使用固定大小的内存块。当你创建一个新矩阵时,比如使用zeros 函数,你为矩阵及其元数据分配了足够的空间。如果您查看 MATLAB 用于附加到矩阵的符号,它几乎可以解释自己:

    >> a = zeros(1, 3)
    
    a =
    
         0     0     0
    
    >> a = [a 1]
    
    a =
    
         0     0     0     1
    

    您创建了一个新矩阵[a 1](顺便说一下,它是horzcat 的别名),然后将其存储在a 中。但是,您不需要将其存储回a,在这种情况下,您会在内存中浮动两个矩阵。每次连接任意大小的矩阵时都会发生这种情况。

    另一个指示发生了什么是变量检查器中列出的矩阵大小。如果您注意到,它从不声称分配任何东西,除了为数据和元数据提供足够的空间。

    这种设计的目的非常简单。 MATLAB 使用矩阵。矩阵通常不会发生太大变化,但它们确实经常习惯于创建新矩阵。因此,拥有固定大小的数组和良好的分配机制比预测用户扩展数组的愿望更为重要。如果您尝试将矩阵串联放入循环中,MATLAB 分析器会告诉您同样多的信息。实验会告诉你分析器没有说谎。这根本不是语言的目的。

    以上所有内容也恰好适用于 Python 中的 numpy 数组,只是它有更好的文档记录。

    在相关说明中,请记住 MATLAB 与 Java 完美集成,因此如果您需要一个管理良好、可扩展的数组,您可以直接在 MATLAB 中使用java.util.ArrayList

    【讨论】:

    • 我确实明白你的观点,如果你在第二个进程之后查看a,MATLAB 会将其列为 1x4 数组,但我认为它可能在幕后为潜在的, 未来的串联。
    • 出于什么目的?当你追加一些东西时,你实际上是在创建一个新矩阵。 MATLAB 的工作假设是您希望保持旧版本保持原样。事实上,即使按照我在这里写的方式,horzcat 也会返回一个新矩阵,这意味着在您返回并覆盖a 之前会有一个短暂的时间,此时旧版本和新版本同时存在。跨度>
    【解决方案3】:

    我拼凑了一个测试函数来推断有关动态分配的一些更具体的细节。使用的函数在答案的底部。

    该函数调用多个位置函数,这些函数根据具有不同对数间隔长度的输入 x 计算值 y。功能不同,但循环类型(forwhile)和分配方案(动态和预分配)。结果来自 R2014b。 四个函数的运行时间结果如下所示。

    对于低元素计数,运行时间是恒定的;对于高元素计数,所有四个函数都进入了具有相似增长率的增长状态。 数据的幂拟合(即c = ArgMin[c(1)*n^c(2) - time])返回四个数据集的指数范围为 0.93 - 0.96(线性增长)。

    我对这些结果感到非常惊讶,因为要么是我在测试中遗漏了一些东西,要么是 Matlab 的 JIT 编译器非常擅长分配数组(可能是底层的链表)。 预分配的for-loop 确实运行得最快,但只比动态版本快 12%。

    谈到内存消耗,我运行了for-loop 变体的动态和预分配版本,用于在一千万个元素范围内的几个线性增加的元素计数。 Matlab 使用的总内存是在循环的开始和结束时采样的。 结果如下所示。

    如图所示,函数调用的起始内存在两个函数之间(大部分情况下)是相同的。 预分配版本的内存使用量随元素数量线性增加,这是预期的。 但是用于动态版本的内存,虽然仍然是分段和趋势线性的,但在内存的斜率中在几个点上有跳跃。 对我来说,这意味着 Matlab 正在做某种形式的表增长(不一定像我认为 Python 那样加倍表)而不是每次迭代都重新分配数组(这确实让我质疑我对上述链接列表的想法时间讨论)。


    我发现这些结果很有趣,但除了上述想法之外,我无法真正得出更多结论。 但是,无论如何,显式预分配对于任何数字繁重的应用程序中的运行时和代码维护总是更好。

    当然,欢迎任何关于结果和测试功能的 cmets(可能讨论这个测试是如何完全错误的)。这就是我(我们)学习的方式。


    function [] = test()
    
        %   Constants and setup
        funs   = {@forDynamic,@forAllocate,@whileDynamic,@whileAllocate};
        Nfun   = numel(funs)                                            ;
        %
        Nalloc = 2E7                                                    ;
        Nsamp  = 50                                                     ;
        %
        x      = linspace(0,1,Nalloc)                                   ;
        nIndex = round(logspace(1,log10(Nalloc),Nsamp))                 ;
        times  = repmat({zeros(1,Nsamp)},1,Nfun)                        ;
    
        %   Array growth time-data
        for k = 1:numel(funs)
            f = funs{k};
            for m = 1:Nsamp
                tic;
                f(x(1:nIndex(m)));
                times{k}(m) = toc;
                fprintf(['Iteration ',num2str(m,'%02G'),' of function ',num2str(k),' done.\n']);
            end
        end
    
        %   Plot
        figure(1);
            args(2:2:2*Nfun) = times;
            args(1:2:2*Nfun) = repmat({nIndex},1,Nfun);
            loglog(args{:});
            legend('Dynamic Allocation (for)','Pre-Allocation (for)','Dyanmic Allocation (while)','Pre-Allocation (while)','Location','Northwest');
            grid('on');
            axis([nIndex(1),nIndex(end),0.95*min([times{:}]),1.05*max([times{:}])]);
            xlabel('Number of Array Elements [-]');
            ylabel('Elasped Time [s]');
    
    
        %   Switch to linear scale near allocation max and only look at for-functions
        Nsamp  = 50  ;
        nIndex = round(10.^linspace(log10(Nalloc)-1.5,log10(Nalloc),Nsamp)) ;
        mstart = repmat({zeros(1,Nsamp)},1,Nfun/2)                          ;
        mend   = repmat({zeros(1,Nsamp)},1,Nfun/2)                          ;
    
        %   Array growth memory-data
        for k = 1:numel(funs)/2
            f = funs{k};
            for m = 1:Nsamp
                [~,mstart{k}(m),mend{k}(m)] = f(x(1:nIndex(m)));
                fprintf(['Iteration ',num2str(m,'%02G'),' of function ',num2str(k),' done.\n']);
            end
        end
    
        %   Plot
        figure(2);
            mem              = [mstart,mend];
            args(2:2:2*Nfun) = mem          ;
            args(1:2:2*Nfun) = repmat({nIndex},1,Nfun);
            h = plot(args{:});
            set(h([1,2]),'LineStyle','--');
            set(h([1,3]),'Color','k');
            set(h([2,4]),'Color','r');
            legend('Dynamic Allocation Start','Pre-Allocation Start','Dynamic Allocation End','Pre-Allocation End','Location','Northwest');
            grid('on');
            axis([nIndex(1),nIndex(end),0.95*min([mem{:}]),1.05*max([mem{:}])]);
            xlabel('Number of Array Elements [-]');
            ylabel('Matlab Memory Usage [MB]');
    
    end
    
    
    function y = burden(x)
        y = besselj(0,x);
    end
    
    function mem = getMemory()
        mem = memory();
        mem = mem.MemUsedMATLAB/1E6; %[MB]
    end
    
    function [y,mstart,mend] = forDynamic(x)
    
        mstart = getMemory();
        n      = numel(x)   ;
        for k = 1:n
            y(k) = burden(x(k));
        end
        mend = getMemory();
    
    end
    function [y,mstart,mend] = forAllocate(x)
    
        mstart = getMemory();
        n      = numel(x)   ;
        y(1,n) = 0          ;
        for k = 1:numel(x)
            y(k) = burden(x(k));
        end
        mend = getMemory();
    
    end
    function [y,mstart,mend] = whileDynamic(x)
    
        mstart = getMemory();
        n      = numel(x)   ;
        k      = 1          ;
        while k <= n
            y(k) = burden(x(k)) ;
            k    = k + 1        ;
        end
        mend = getMemory();
    
    end
    function [y,mstart,mend] = whileAllocate(x)
    
        mstart = getMemory();
        n      = numel(x)   ;
        k      = 1          ;
        y(1,n) = 0          ;
        while k <= n
            y(k) = burden(x(k)) ;
            k    = k + 1        ;
        end
        mend = getMemory();
    
    end
    

    【讨论】:

      猜你喜欢
      • 2012-07-13
      • 1970-01-01
      • 2017-04-15
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-04-02
      相关资源
      最近更新 更多