【问题标题】:What is the alternative to preallocating arrays in MATLAB? [duplicate]在 MATLAB 中预分配数组的替代方法是什么? [复制]
【发布时间】:2010-07-14 23:04:53
【问题描述】:

可能重复:
Growable data structure in MATLAB

所以在我当前的 MATLAB 脚本中,我有一个非常大的不确定大小的增长数组。目前我对此无能为力,因为如果我真的预先分配,它将占用比它应该需要的多很多倍的内存(最大可能的值是每像素 640 个,但通常它是 2- 5).

通常在这种情况下,我会在 C++ 或其他东西中使用向量,它相对于给定容量呈指数增长。但我认为 Matlab 中的矩阵开始分段的速度比目的驱动的 C++ 向量快得多。

你们认为这样的事情最好的选择是什么?还是我应该坚持使用普通数组并希望按顺序添加大约 10 万个元素会起作用?

提前致谢。

【问题讨论】:

标签: arrays optimization memory matlab


【解决方案1】:

增加一个数组通常是一件坏事,至少如果你会做很多次的话。有一些技巧可以使用。 (我已经尝试了所有这些,并编写了可以根据需要自动为您执行此操作的工具。)

问题在于与数组增长相关的时间是 O(N^2) 操作。它还往往会分散您的记忆,如果您以后需要创建一个大型数组,则会给您留下潜在的问题。

一种方法是将数组预先分配到某个固定大小,然后在超出数组大小时附加大块零。现在使用矩阵索引将新值插入现有数组。这个想法是在需要重新分配时将数组的大小加倍。这只会导致很少的重新分配,但最终可能会附加很多您不需要填充的零。最后,您转储无关的和未使用的数组元素。

第二个技巧是使用元胞数组。每次您想添加一个新元素或其中的块时,您只需添加一个带有这些元素的新单元格。然后在最后,你做一个 cell2mat 操作。单元格技巧的一个问题是它仍然是 O(N^2) 操作,因为单元格数组本身本质上是一个指针数组,并且在追加新元素时必须重新分配指针列表。

你可以做一个我喜欢的组合技巧,但它需要一个工具来实现操作。这个想法是创建包含 10000 个元素块的单元格。用零填充第一个块。然后使用矩阵索引在生成新元素时插入它们。矩阵索引当然很快。当您在该单元格中用完空间时,您会追加一个新的大单元格。最后,将所有单元格连接在一起,小心地丢弃未使用的元素。

最后一种方案涉及的新单元格元素相对较少,因此如果可能有数百万个附加步骤,则总体上可能是最有效的。

这些方法中的每一种都体现在我多年来发布的几份文件交换提交中。第一个可以找到grow_array,它在内部管理附加步骤,为您担心矩阵索引。

后来,我使用函数句柄或持久变量构建了最后一个方案来维护存储的信息。包含这些实现的工具位于growdata and growdata2

【讨论】:

  • +1 获取详细答案
【解决方案2】:

您可以尝试 std::vector 在重新分配元素时所做的事情 --- 每次填满时将其容量加倍,其摊销成本为 O(1)

测试:

function test_MAT_vector

    LIM = 5e4;
    %%# Normal
    tic;
    A = zeros(1,1);
    for i = 1:LIM
        A(end+1) = i;
    end
    toc

    %%# std::vector clone
    tic;
    B = zeros(1,1);
    for i = 1:LIM
        if(i > numel(B))
            B = [B;zeros(numel(B),1)];
        end
        B(i) = i;
    end
    toc

end

输出:

经过的时间是 3.489820 秒。

经过的时间是 0.006710 秒。

使用单元格

%%# cell
tic;
A = cell(1,1);
for i = 1:LIM
    A(end+1) = {i};
end
toc

经过的时间是 2.740792 秒。

【讨论】:

  • 这让我很伤心,我要这样做,但这看起来是一个有效的解决方案。谢谢
  • 或者我应该只用一个 mexfile 包装一个 std::vector ... <_>
【解决方案3】:

在上一个问题中,我已将a similar solution 发布到@Jacob 提出的问题中。

我后来比较了大多数可用选项的性能(上面的@woodchips 很好地总结了这些)。以下是我在我的机器上得到的结果:

NUM = 50000;

%%# ========== MINE: ~0.07sec ==========
tic
BLOCK_SIZE = 2000;                           %# initial & increment size
listSize = BLOCK_SIZE;                       %# current list capacity
list = zeros(listSize, 2);                   %# actual list
listPtr = 1;                                 %# pointer to last free position
for i=1:NUM
    %# push items on list
    list(listPtr,:) = [rand rand];           %# store new item
    listPtr = listPtr + 1;                   %# increment position pointer
    %# add new block of memory if needed
    if( listPtr+(BLOCK_SIZE/10) > listSize ) %# less than 10%*BLOCK_SIZE free
        listSize = listSize + BLOCK_SIZE;    %# add new BLOCK_SIZE slots
        list(listPtr+1:listSize,:) = 0;
    end
end
list(listPtr:end,:) = [];                    %# remove unused slots
toc

%%# ========== PREALLOCATION (matrix): ~0.05sec ==========
tic
list = zeros(NUM,2);
for i=1:NUM
    list(i,:) = [rand rand];
end
toc

%%# ========== PREALLOCATION (cell): ~1.1sec ==========
tic
list = cell(NUM,1);
for i=1:NUM
    list{i} = [rand rand];
end
list = vertcat( list{:} );
toc

%%# ============ NO-PREALLOCATION (grow cell): ~5sec ========
tic
list = {};
for i=1:NUM
    list{i} = [rand rand];
end
list = vertcat( list{:} );
toc

%%# ============ NO-PREALLOCATION (grow matrix): ~24sec ========
tic
list = [];
for i=1:NUM
    list(i,:) = [rand rand];
end
toc

%%# ========== GROWDATA (by John D'Errico): ~3.3sec =========
tic
growdata                             %# The initialization call
for i = 1:NUM
    growdata( [rand rand] )          %# push items
end
list = growdata;                     %# unpacking step
toc

【讨论】:

  • 我要补充一点,简单的细胞解决方案对于相对少量的细胞是有效的。这是几千个细胞的好方法。即使对于 50k 单元,这仍然很快。当您开始进入数百万个附加时,具有数百万个单元的单元阵列可能开始变得更加昂贵。您仍然需要增加该指针数组。 (我记得,在过去的 matlab 版本中,这个故事有所不同,我发现盈亏平衡点较低,所以我想随着时间的推移他们已经改进了。)
  • @woodchips:看来你是对的,在迭代次数上再添加几个零,增长的细胞解决方案会变得更慢。顺便说一句,我可能应该使用cell2mat(list) 而不是vertcat(list{:}),因为它的速度要快一个数量级..
【解决方案4】:

您可以生成比数组便宜得多的单元数组,然后使用cell2mat 转换为数组(需要两倍的内存,但如果有的话,这可能是最简单的方法)。

如果您知道可以预分配正确大小的单元格数组的像素数,请使用实际值数组(最多 640,但通常为 2-5)填充每个单元格,然后使用 cell2mat 将其转换为一个连续的数组。

如果您担心碎片,您可以在加载单元后执行pack,该单元应该对您的内存进行碎片整理(将所有内容都写入磁盘并再次连续加载),然后再进行cell2mat 转换。

【讨论】:

  • 没错,但速度很慢(我已经在我的回答中对其进行了分析)。
  • @Jacob:对于像你的例子这样的单个元素来说它很慢,但我通常在想要连接更大的数组时使用这种方法,而数组扩展真的会杀死你......即在你的测试中而不是添加单个元素例如,添加一个长度为 100000 - 1000000 的随机数组。您也可以预先分配单元格数组。我认为你会发现这比数组扩展快得多,但我敢肯定仍然不如你的 std::vector 克隆快(尽管在这种情况下有点复杂)
猜你喜欢
  • 2014-05-20
  • 2020-08-28
  • 1970-01-01
  • 1970-01-01
  • 2017-03-09
  • 2023-03-07
  • 2017-02-01
  • 2020-07-18
  • 1970-01-01
相关资源
最近更新 更多