octave: Indexed Assignment Optimization

 
 34.3.2 Indexed Assignment Optimization
 --------------------------------------
 
 Octave’s ubiquitous lazily-copied pass-by-value semantics implies a
 problem for performance of user-defined ‘subsasgn’ methods.  Imagine the
 following call to ‘subsasgn’
 
      ss = substruct ("()", {1});
      x = subsasgn (x, ss, 1);
 
 where the corresponding method looking like this:
 
      function x = subsasgn (x, ss, val)
        ...
        x.myfield (ss.subs{1}) = val;
      endfunction
 
    The problem is that on entry to the ‘subsasgn’ method, ‘x’ is still
 referenced from the caller’s scope, which means that the method will
 first need to unshare (copy) ‘x’ and ‘x.myfield’ before performing the
 assignment.  Upon completing the call, unless an error occurs, the
 result is immediately assigned to ‘x’ in the caller’s scope, so that the
 previous value of ‘x.myfield’ is forgotten.  Hence, the Octave language
 implies a copy of N elements (N being the size of ‘x.myfield’), where
 modifying just a single element would actually suffice.  In other words,
 a constant-time operation is degraded to linear-time one.  This may be a
 real problem for user classes that intrinsically store large arrays.
 
    To partially solve the problem Octave uses a special optimization for
 user-defined ‘subsasgn’ methods coded as m-files.  When the method gets
 called as a result of the built-in assignment syntax (not a direct
 ‘subsasgn’ call as shown above), i.e., ‘x(1) = 1’, AND if the ‘subsasgn’
 method is declared with identical input and output arguments, as in the
 example above, then Octave will ignore the copy of ‘x’ inside the
 caller’s scope; therefore, any changes made to ‘x’ during the method
 execution will directly affect the caller’s copy as well.  This allows,
 for instance, defining a polynomial class where modifying a single
 element takes constant time.
 
    It is important to understand the implications that this optimization
 brings.  Since no extra copy of ‘x’ will exist in the caller’s scope, it
 is _solely_ the callee’s responsibility to not leave ‘x’ in an invalid
 state if an error occurs during the execution.  Also, if the method
 partially changes ‘x’ and then errors out, the changes _will_ affect ‘x’
 in the caller’s scope.  Deleting or completely replacing ‘x’ inside
 subsasgn will not do anything, however, only indexed assignments matter.
 
    Since this optimization may change the way code works (especially if
 badly written), a built-in variable ‘optimize_subsasgn_calls’ is
 provided to control it.  It is on by default.  Another way to avoid the
 optimization is to declare subsasgn methods with different output and
 input arguments like this:
 
      function y = subsasgn (x, ss, val)
        ...
      endfunction