【问题标题】:Can I prevent Matlab from dynamically resizing a pre-allocated array?我可以阻止 Matlab 动态调整预分配数组的大小吗?
【发布时间】:2013-10-03 00:15:20
【问题描述】:

例如,在这个简单/愚蠢的例子中:

n = 3;
x = zeros(n, 1);
for ix=1:4
    x(ix) = ix;
end

数组是预先分配的,但在循环中动态调整大小。 Matlab 中是否有一个设置会在发生这样的动态调整大小时引发错误?在这个例子中,我可以简单地重写它:

n = 3;
x = zeros(n, 1);
for ix=1:4
    if ix > n
        error('Size:Dynamic', 'Dynamic resizing will occur.')
    end
    x(ix) = ix;
end

但我希望以此作为检查,以确保我已正确预分配矩阵。

【问题讨论】:

  • 你为什么不循环for ix=1:n?这样一来,如果您碰巧将n 设置为错误的值,您只需修复一行代码。
  • 澄清一下,涉及手动放置assert 语句或引发错误的变通办法,甚至封装该操作的函数都行不通。程序员可能有另一个变量y,其中动态调整大小是可以的,并且一直在检查y 时性能损失是不好的。在这种情况下,程序员必须手动决定,对于每个潜在的动态调整大小操作和每个不同的数据变量,是使用基于断言/错误/函数的调用还是只使用基本的 Matlab 调用,从而导致复制/粘贴和不可读的代码。
  • @EMS 您抱怨其他人建议的丑陋解决方法,但这就是解决方法,丑陋。他们会很清楚,他们将是一个解决方案,而不是解决方法。我还没有从你那里听到真正的解决方案,你只是给了一个 python 的链接。不要因为这些丑陋的建议而责怪我们,责怪 Mathworks ...
  • 我建议如果可行的话,真正使用 Python / NumPy 是最好的解决方案。这可能不是由于项目规模,这将是不幸的。在这种情况下,最好的解决方案是创建一个封装此索引检查逻辑的结构或类,然后忍受性能下降。解决方案不会是复制/粘贴或手动功能应用程序。这不仅仅是一种解决方法,它是一个无法回答的问题。如果您受到性能和项目规模的限制(因此切换到更适合的编程环境是不可行的) ) 那么问题就不容易解决
  • @EMS 不存在好的解决方案的答案(据我所知)也是一个有效的答案,即使这不是您所希望的。

标签: matlab


【解决方案1】:

您可以创建double 的子类并限制subsasgn 方法中的赋值:

classdef dbl < double
    methods
        function obj = dbl(d)
            obj = obj@double(d);
        end

        function obj = subsasgn(obj,s,val)
            if strcmp(s.type, '()')
                mx = cellfun(@max, s.subs).*~strcmp(s.subs, ':');
                sz = size(obj);
                nx = numel(mx);
                if nx < numel(sz)
                    sz = [sz(1:nx-1) prod(sz(nx:end))];
                end
                assert(all( mx <= sz), ...
                    'Index exceeds matrix dimensions.');
            end
            obj = subsasgn@double(obj, s, val);
        end

    end
end

所以现在当你预分配使用dbl

>> z = dbl(zeros(3))
z = 
  dbl

  double data:
     0     0     0
     0     0     0
     0     0     0
  Methods, Superclasses

double 的所有方法现在都由dbl 继承,您可以照常使用它,直到您将某些内容分配给z

>> z(1:2,2:3) = 6
z = 
  dbl

  double data:
     0     6     6
     0     6     6
     0     0     0
  Methods, Superclasses

>> z(1:2,2:5) = 6
Error using dbl/subsasgn (line 9)
Index exceeds matrix dimensions.

我尚未对其进行基准测试,但我预计这对性能影响不大。

如果您希望显示的值看起来正常,您也可以重载 display 方法:

function display(obj)
    display(double(obj));
end

然后

>> z = dbl(zeros(3))
ans =
     0     0     0
     0     0     0
     0     0     0
>> z(1:2,2:3) = 6
ans =
     0     6     6
     0     6     6
     0     0     0
>> z(1:2,2:5) = 6
Error using dbl/subsasgn (line 9)
Index exceeds matrix dimensions.
>> class(z)
ans =
dbl

【讨论】:

  • 非常好,我认为类是实现这一目标的唯一正确方法。请注意,此解决方案仍然允许您增加变量的大小,而不是通过使用超出其大小的数字对其进行索引。很高兴知道x = dbl(magic(5)); x=x(:); x=[x x]; x= dbl(rand(100)); 之类的东西会起作用。
  • 注意冒号运算符 - 您的代码会将其转换为 ASCII 值。简单修复:分配后mx(cellfun(@(x) strcmp(x,':'),s.subs)) = 1;。在一些非常快速和粗略的测试中,这比让 Matlab 动态分配数组慢约 2 倍,或者比其预先分配的双倍性能慢约 3 倍。
  • @Matt B 你能提供这方面的任何统计数据吗?它是否从根本上改变了复杂性?也就是说,对于 O(n) 重新分配,这是否只是使它在 n 前面成为一个较慢的常数,还是使它成为 O(n^2) 等等。对于涉及访问的其他数组操作呢?混合操作怎么样,比如将这些 dbl 对象之一附加到其他仍然可以动态调整大小的数组上?
  • 您不需要继承double,而是可以通过创建@double 目录并将subsasgn 函数添加到其中来重载subsasgn。它仍然会给您带来相同的性能影响。
  • @DanielE.Shub 不,您的解决方案不起作用,在documentation for subsasgn 的描述部分的第三段中明确提到了它作为示例
【解决方案2】:

这不是一个完整的示例(请参阅代码后的免责声明!)但它显示了一个想法......

您可以(至少在调试代码时)使用以下类代替零来分配原始变量。

随后使用超出原始分配大小范围的数据将导致“索引超出矩阵维度”。错误。

例如:

>> n = 3;
>> x = zeros_debug(n, 1)

x = 

     0
     0
     0

>> x(2) = 32

x = 

     0
    32
     0

>> x(5) = 3
Error using zeros_debug/subsasgn (line 42)
Index exceeds matrix dimensions.

>> 

类代码:

classdef zeros_debug < handle    
    properties (Hidden)
       Data
    end

    methods       
      function obj = zeros_debug(M,N)
          if nargin < 2
              N = M;
          end
          obj.Data = zeros(M,N);
      end

        function sref = subsref(obj,s)
           switch s(1).type
              case '()'
                 if length(s)<2
                 % Note that obj.Data is passed to subsref
                    sref = builtin('subsref',obj.Data,s);
                    return
                 else
                    sref = builtin('subsref',obj,s);
                 end              
               otherwise,
                 error('zeros_debug:subsref',...
                   'Not a supported subscripted reference')
           end 
        end        
        function obj = subsasgn(obj,s,val)
           if isempty(s) && strcmp(class(val),'zeros_debug')
              obj = zeros_debug(val.Data);
           end
           switch s(1).type
               case '.'
                    obj = builtin('subsasgn',obj,s,val);
              case '()'
                    if strcmp(class(val),'double')                        
                        switch length(s(1).subs{1}) 
                            case 1,
                               if s(1).subs{1} > length(obj.Data)
                                   error('zeros_debug:subsasgn','Index exceeds matrix dimensions.');
                               end
                            case 2,                            
                               if s(1).subs{1} > size(obj.Data,1) || ...
                                       s(1).subs{2} > size(obj.Data,2) 
                                   error('zeros_debug:subsasgn','Index exceeds matrix dimensions.');
                               end                            
                        end
                        snew = substruct('.','Data','()',s(1).subs(:));
                             obj = subsasgn(obj,snew,val);
                    end
               otherwise,
                 error('zeros_debug:subsasgn',...
                    'Not a supported subscripted assignment')
           end     
        end        
        function disp( obj )
            disp(obj.Data);
        end        
    end   
end

会有相当大的性能影响(以及使用从句柄继承的类产生的问题),但这似乎是对原始问题的一个有趣的解决方案。

【讨论】:

    【解决方案3】:

    允许分配给数组边界之外的索引并用零填充空白确实是 Matlab 的丑陋部分之一。除了实现自己的存储类之外,我不知道没有明确检查以避免这种情况的任何简单技巧。我会坚持在你的循环中添加一个简单的 assert(i &lt;= n) 并忘记它。我从来没有因为分配超出范围的东西而被难以找到的错误所困扰。

    如果预分配被遗忘或太小,在“理想”情况下,由于二次行为,您的代码会变得非常缓慢,之后您会发现错误并修复它。但是现在,Matlab 的 JIT 有时足够聪明,不会导致任何减速(在某些情况下它可能会动态增长数组,例如 python 的列表),所以它甚至可能不再是一个问题。所以它实际上允许一些草率的编码......

    【讨论】:

    • 如果您想在许多不同的 for 循环中执行此检查。您必须复制/粘贴 assert 代码。如果您改用函数,则必须手动记住使用该函数进行索引检查但仅限于该特定数据片段。无论哪种方式,检查都不是由数据结构隐式完成的,而是由程序员的外生知识手动完成的。这些违反了长期存在的软件设计原则。
    【解决方案4】:

    我能想到的最简单、最直接和最可靠的方法就是在分配索引之前访问它。不幸的是,您不能为基本类型重载 subsasgn(在任何情况下正确执行都会令人头疼)。

    for ix=1:4
        x(ix); x(ix) = ix;
    end
    % Error: 'Attempted to access x(4); index out of bounds because numel(x)=3.'
    

    或者,您可以尝试变得聪明并使用end 关键字做一些事情......但无论您做什么,您最终都会收到某种无意义的错误消息(上面很好地提供了)。

    for ix=1:4
        x(ix*(ix<=end)) = ix;
    end
    % Error: 'Attempted to access x(0); index must be a positive integer or logical.'
    

    或者您可以在一个函数中进行检查,这会为您提供不错的错误消息,但仍然非常冗长和混淆:

    for ix=1:4
        x(idxchk(ix,end)) = ix;
    end
    function idx = idxchk(idx,e)
        assert(idx <= e, 'Size:Dynamic', 'Dynamic resizing will occur.')
    end
    

    【讨论】:

    • 如果您想在许多不同的 for 循环中执行此检查。你必须复制/粘贴代码。如果您改为使用该函数,则必须手动记住使用该函数进行索引检查但仅限于该特定数据片段。无论哪种方式,检查都不是由数据结构隐式完成的,而是由程序员的外生知识手动完成的。
    • @EMS 我的声明“你不能为基本类型重载 subsasgn”是一个非常困难和真正的限制。请参阅subsasgn 上的文档。然后,替代方法是使用自定义 subsasgn 行为定义您自己的类(但自定义 matlab OOP 是 slow)或使用某种解决方法。我只是头脑风暴一些。不,该函数可用于任何数据结构,因为它会检查 end 关键字。
    • 但是你特别说,“我能想到的最简单、最直接和最可靠的方法就是在分配索引之前访问它。”这可能很简单,但并不简单(因为您必须在代码中需要它的任何地方复制.粘贴它)并且它绝对不可靠(因为它依赖于用户识别索引检查适用于哪些变量并且它不适用)。
    • 换句话说,这充其量是一个完整的解决方法,在我看来,甚至不是问题的答案(根据我对问题本身的cmets,这样的事情总共是non-answer 从程序设计的角度来看。事实上,我会说这种答案实际上对程序有害,因为它引入了比其他更多错误的机会它解决了。)
    • @EMS,最初的问题只是要求“检查以确保我已经正确地预先分配了我的矩阵。”我读这篇文章是为了寻找一种快速的方法来仔细检查索引,而不必知道每个数据结构的长度,而不是一些持久的解决方案。我的第一个建议是简单地获取 LHS 的副本:快速、简单和简单。我不提倡在生产代码中使用这种解决方案。但作为快速检查?当然。
    猜你喜欢
    • 2020-11-26
    • 2010-12-13
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-09-12
    • 1970-01-01
    • 2021-07-11
    • 2022-10-25
    相关资源
    最近更新 更多