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 function optimize_subsasgn_calls
is provided to
control it. This feature is enabled 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