Table of Contents
xff - memory management
Motivation
Matlab's only way of passing arguments into a called function is “by-value”, which means that if the called function alters a variable (which can be highly complex, such as a nested struct!), the calling function needs to accept any such altered variables as returned values and re-assign them to their original variables. Here is an example:
- matlab_pass_by_value.m
% the function called is fictitious, and would replace % bad coordinate values in variables X, Y, and Z with % NaNs in all three of them [X, Y, Z] = nanbadcoords(X, Y, Z);
While this is relatively convenient (and Matlab's internal calling syntax detects that variables are to be overwritten, so unless an error occurs, there is little memory overhead), this is highly unusual if objects are used. Most object-oriented languages support a “call-by-reference” syntax, where the calling function only receives a reference or pointer to the actual object and any changes are thus automatically propagated to the calling function (and any function up the stack!).
Implementation
In xff (and also the other NeuroElf classes, for that matter), storage is not allocated in the struct variable that constitutes the class object (which is true for most other non-NeuroElf class objects!). The only field in the struct variable of any xff object is the .L
field which contains a unique lookup identifier that allows xff and its internal methods to locate the actual storage of the object:
% creating an object vmr = xff('new:vmr'); % displaying the struct contents: struct(vmr)
This would produce something like the following sample output:
ans = L: 0.6692
The actual storage is kept in a global variable, xffcont
, which I recommend against altering manually:
% intialize xff xff; % display list of global variables whos global
would produce (if no other global variables are present):
Name Size Bytes Class Attributes xffclup 1x1 8 double global xffconf 1x1 29219 struct global xffcont 1x1 2700694 struct global xfflast 1x2 16 double global xinimeth 1x1 5342 struct global
These variables have the following content/meaning:
xffclup
- content lookup list (contains one double number per loaded object, one of which is the ROOT object)xffconf
- xff configuration (global instead of persistent as it is used by some methods also)xffcont
- the actual storage of objects as a vector of structs, each having the fields- C - actual content (what is displayed when an object variable is used without a semicolon)
- F - filename (empty if not loaded from disk and not saved yet, “
<ROOT>
” for the ROOT object) - H - sub-struct of handles to other objects (e.g. transimg objects for data slicing, etc.)
- L - lookup value (for convenience stored also in the struct)
- S - file format specification (extension(s), fields, etc.)
- U - unwind stack information (used for the internal automatic garbage collection of xff)
- xfflast - 1×2 double value, for which object was the fieldnames/methods function called last and second-to-last
- xinimeth - method name storage of xini class; as xff uses xini for some settings, this class is also initialized
Consequences
As the storage is NOT directly associated with any given object variable, using Matlab's clear function on any such variable will not lead to the allocated memory being freed by Matlab! Instead the .ClearObject
call must be issued to free up memory:
- xff_clearobject_sample.m
% create a new VMR vmr = xff('new:vmr'); % set random content vmr.VMRData = uint8(100 + round(10 * randn(size(vmr.VMRData)))); % save as (without argument requests for filename) vmr.SaveAs; % clear object!! vmr.ClearObject;
Without this last line, the associated memory would remain allocated even if this is used in a sub-function (which removes the vmr object from memory, but that only contains a struct with the .L
field!).
Garbage collection
To ensure that user-written functions do not clutter up the global xffcont
variable with objects that are no longer in use, a garbage collection has been implemented into xff. This works as follows:
- for any object, the stack is recorded during object creation (which function created the object)
- whenever any xff method is called, the stack of all remaining objects is checked, and if the current stack doesn't match, those objects are removed
- to protect objects from being affected, the
bless
function must be called
bless
Here is an example of a user-written function that creates an object which is suitable for passing outside of the function and not being affected by this garbage collection (it also creates another object which would be removed by garbage collection if the .ClearObject
method would not be called):
- spheresvmr_sample.m
function vmr = spheresvmr_sample % spheresvmr_sample - create a VMR with 10 small spheres % % FORMAT: vmr = spheresvmr_sample; % % No input fields. % % Output fields: % % vmr VMR object with 10 spheres % create VMR object (output) vmr = xff('new:vmr'); % create VOI object as a helper voi = xff('new:voi'); % create 10 spheres in VOI for c = 1:10 voi.AddSphericalVOI(min(120, max(-120, round(40 * randn(1, 3)))), 7); end % set those coordinates to 200 in VMR for c = 1:10 vmr.VMRData(bvcoordconv(voi.VOI(c).Voxels, 'tal2bvc', vmr.BoundingBox)) = 200; end % make sure VMR is not affected by garbage collection bless(vmr, 1); % destroy voi (would be removed by garbage collection some time later otherwise) voi.ClearObject; % end of function