Indexed Assignment Optimization - GNU Octave
Description: GNU Octave

Previous: Defining Indexing And Indexed Assignment, Up: Indexing Objects

#### 33.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 a call to subsasgn:

           ss = substruct ("()",{1});
           x = subsasgn (x, ss, 1);

and 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, i.e., degrades a constant-time operation 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 direct subsasgn call as shown above), i.e.

           x(1) = 1;

AND if the subsasgn method is declared with identical input and output argument, like 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` in the caller's scope will exist, it is solely the callee's responsibility to not leave `x` in an invalid state if an error occurs throughout 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 option to avoid the effect is to declare subsasgn methods with different output and input arguments, like this:

           function y = subsasgn (x, ss, val)
             ...
           endfunction