MATLAB如何处理动态数组分配?

时间:2015-09-24 16:28:08

标签: arrays matlab memory-management

我不熟悉MATLAB,并且很好奇它是如何处理动态内存分配引擎的?

一种主要方法是分配大块,而不是必要的,这样就不必为添加的每个新元素分配。在进行一些研究时,我看到很多人亲自管理他们自己的大块分配(假设他们不知道他们的最终大小)或做了一些事情,比如创建最大尺寸,然后修剪。一个例子是Undocumented MATLAB,建议您自己执行块内存分配。我本以为像MATLAB这样的语言会知道这样做,我不会被要求关心自己这样的问题。这对我来说意味着如果你试图将一个新元素附加到一个数组上,MATLAB只为那个单独的元素分配新的内存,这是非常低效的。

我的问题是双重的

  • 对于动态数组,MATLAB是否分配了大块,并且存储的内存多于节省计算效率所需的内存,还是只为连接的内容分配内存?
  • 如果前者是这种情况,那么MATLAB是否选择采用这种设计?

3 个答案:

答案 0 :(得分:4)

我回忆起几年前在Matlab Expo上他们谈到了总部正在开发的事情 - 其中一个是自动预分配的记忆。

没有提及何时可能会释放它,或者即使它会发生......而且我从未听说过它......

根据我的经验 - 我总是自己管理动态分配 - 并且如果代码的那部分出现问题(例如,当我认为它们不应该......时阵列正在增长)时总是注意到严重减速。 p>

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

答案 1 :(得分:3)

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

答案 2 :(得分:2)

我将测试函数汇总在一起,以推断出有关动态分配的更具体细节。使用的函数位于答案的底部。

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

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

我对这些结果感到非常惊讶,因为我在测试中缺少某些内容,或者Matlab的JIT编译器非常擅长分配数组(可能是链接列表)。 预先分配的for - 循环运行速度最快,但仅比动态版本快12%。

Elapsed run time versus element count

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

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

Memory usage versus element count

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

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

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