【问题标题】:Variable appears to change size on every loop iteration - what?变量似乎在每次循环迭代时都会改变大小 - 什么?
【发布时间】:2014-01-28 04:16:20
【问题描述】:

在编写以下 Matlab 代码时:

for ii=1:n
    x(ii) = foo( ii ); % foo is some function of ii that cannot be vectorized.
end

我收到以下m-lint 警告:

变量x 似乎在每次循环迭代时都会改变大小

我的问题:

  1. 该警告是什么意思?
  2. 为什么每次迭代都改变变量大小是一件坏事?
  3. 如何解决这个问题?

这个问题不是this one 的重复,因为它处理的是预分配的更一般方面,而不是它的一个具体实例。

【问题讨论】:

标签: matlab memory-management iteration


【解决方案1】:

嗯,第一件事。

1。这个警告是什么意思?

这段代码在语法上是正确的,它将正确执行并返回预期的结果:x 的ii-th 元素将包含值foo( ii )。
但是,在这段小代码运行之前,变量x 是没有定义的。现在,当循环开始时,x(1) 被赋值为 foo( 1 ),因此 Matlab 创建了 x 作为长度为 1 的数组。在第二次迭代中,x(2) 被赋值为 foo( 2 ),因此 Matlab 需要将 x 的长度更改为 2,依此类推:x 在每次迭代时都会更改其长度/大小。

2。为什么每次迭代都改变变量大小是一件坏事?

考虑当x 每次迭代都改变它的大小时,后台会发生什么(在内存分配方面):在每次迭代中,Matlab 需要找到一个空闲的内存空间来承载x 的新大小。如果幸运的话,x 之后有足够的可用空间,所以所发生的只是改变分配给x 的内存量,并在正确的位置写入新值。
但是,如果在x 之后没有足够的可用空间,Matlab 必须为x 中已经存在的ii-1 元素找到一个新位置,为x 分配这个新空间, 复制 x 中已有的所有ii-1 值到新位置,并释放使用的旧位置x。这种在后台发生的无分配复制操作可能非常耗时,尤其是当x 很大时。

3。如何解决这个问题?

最简单的解决方案是预先分配x需要在循环之前的所有空间:

x = zeros(1,n); 
for ii=1:n
    x( ii ) = foo( ii );
end

通过预分配,我们确定x 预先分配了它需要的所有内存,因此在循环执行时不需要昂贵的内存分配/复制。

该问题的另一种很酷的解决方案

如果你太懒(像我一样)并且不想预先分配,你可以简单地:

for ii=n:-1:1
    x( ii ) = foo( ii );
end

这样,x 第一次被分配一个值,它被分配给它的 n-th 元素(最后一个),因此 Matlab 立即为所有 n 分配空间x的元素。
酷!

【讨论】:

  • 另一种解决方案是右键单击代码的突出显示部分,然后从菜单中选择“Suppress ...”等以停止警告。在某些情况下,动态内存分配无法轻易避免,或者只是不值得进行短暂的廉价计算。在当前版本中,本机动态内存分配可以非常快,甚至比其他一些动态分配方法更快。
  • @horchler 我认为这更像是一种解决方法而不是解决方案。 (取决于你认为问题是什么)。在几乎所有我进行简短计算并且不会导致问题的情况下,我都会反转循环。相当少的努力。
【解决方案2】:

我的回答有点晚了,但是关于 MATLAB 中的数组增长和预分配,我想提几点。

首先要注意的是,MATLAB 在最近的版本中已经大大提高了自动数组增长的性能,所以警告暗示的性能影响可能不会太糟糕如果你做得对(见以下)。不过,最佳做法是预先分配您的数组(例如使用zeros)。

警告解释

从 R2014a 开始,警告的详细说明如下:

指示的变量或数组的大小似乎随着每次循环迭代而变化。通常,出现此消息是因为数组通过赋值或连接而增长。通过赋值或连接来增长数组可能会很昂贵。对于大型数组,MATLAB 必须分配一个新的内存块并将旧数组内容复制到新数组中,因为它会进行每次赋值。

以这种方式更改变量大小的程序可能会将大部分运行时间花在这种低效的活动中。 ...

从这段摘录中可以清楚地看出,如果您完全关心性能,为什么预分配是一个聪明的主意。

旁注:关于数组增长期间用于重新分配的算法的信息有限,但史蒂夫·埃丁斯在同一篇博客文章中提供了一些信息,我在this previous answer 中进行了总结。

自动数组增长优化

如果您想通过沿维度增长(没有预分配)来使用动态数组调整大小,有一些方法可以做到这一点。看到这个MathWorks blog post by Steve Eddins。需要注意的最重要的一点是,您应该沿着最后一个维度发展以获得最佳性能。在您的情况下这不是问题,因为数组是一维的。因此,如果您决定让它继续运行,请将%#ok<SAGROW> 与警告放在同一行,在罪魁祸首代码之后,以使警告静音。

Yair 在another post on his blog 中讨论了动态数组调整大小。此外,还有一些方法可以在不使用一些毛茸茸的 MEX API 技巧进行初始化的情况下分配数组,但仅此而已。

预分配

建议预先分配。养成习惯,学会爱zeros。如果您决心充分利用 MATLAB 的性能,Yair Altman 有几篇关于内存预分配主题的优秀文章:

【讨论】:

  • @LuisMendo 我也喜欢这篇文章。 ;) SO 最烦人的一个方面是,像这样的帖子通常很少得到认可,而像this junk 这样的帖子会得到大量的认可。完全莫名其妙! [耸肩]
  • 我同意这很烦人。就像@Shai 所说,life isn't fair
【解决方案3】:

【讨论】:

    【解决方案4】:

    这个答案被简化了一些细节。

    1. M-lint 在发现有机会预分配内存时会警告程序员。预分配内存缩短了处理时间,因为它简化了与内存硬件管理相关的 MATLAB 程序员直接控制之外的任务。

    2. 不断变化的变量大小并不总是坏事;但是,为更快的处理速度编写代码是一种很好的编程习惯。当代码使用多次迭代或更多内存时,更快的处理速度会更加明显。

    3. 推荐的其他 MATLAB 预分配方法也可以使用;但是,以下方法可能更直观。

      • 为预先分配的数组赋值。

        x(1:n) = NaN; 
        for ii=1:n
            x(ii) = foo(ii); 
        end
        

        注意事项:

        • 在预分配时分配 NaN 可能是有利的,因为在数组中找到 NaN 意味着该值未分配。
        • 在一维中预分配 one(n)、zeros(n) 或 NaN(n) 时,将分配一个 n x n 矩阵。如果使用非常大的数据集,这可能会降低计算速度。
      • 为预先分配的多维数组赋值:

        x(1:n_i,1:n_j) = NaN;  % or NaN(n_i, N_j);
        for ii=1:n_i
            for jj=1:n_j
                x(ii,jj) = foo(ii,jj); 
            end
        end
        
      • 一种懒惰的预分配方法是仅将零分配给数组中的最后一个元素。

        x(n)= 0;
        for ii=1:n
            x(ii) = foo(ii); 
        end
        

    【讨论】:

    • 您可以通过NaN(m,n) 而不是x(1:m,1:n)=NaN 创建一个NaN 数组。
    • 我喜欢使用 x(1:m,1:n)=NaN,因为 NaN()、ones() 或零的一维行为。当在一维中,NaN(m) 分配一个 m × m 矩阵。在处理长数据集时,内存过多会降低程序速度。虽然这种方法更优雅,但它是不一致的。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2011-08-16
    • 2021-10-14
    • 2015-05-18
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多