Class object creation performance

Matlab’s Class Object System (MCOS) is a powerful way to develop maintainable, modular, reusable code using a modern Object Oriented Programming (OOP) paradigm. Unfortunately, using OOP in Matlab carries some performance penalties that need to be considered when deciding whether to code in the new paradigm or keep using the older, simpler procedural paradigm. A major resource in this regard is a detailed post from 2012 by Dave Foti, who heads MCOS development at MathWorks.

As Dave pointed out, the importance of MCOS’s overheads only comes into play when our program uses many class objects and calls many short-duration methods. In the majority of cases, this is actually not the case: Our performance bottlenecks are normally I/O, GUI, memory and processing algorithm – not object manipulation and function-call overheads. Unfortunately, Dave’s article left out many ideas for improving MCOS performance in those cases where it does matter.

Today’s article will expand on the sub-topic of MCOS object creation. While it does not directly rely on any undocumented aspect, I have also not seen it directly explained in the official docs. So consider this to be the missing doc page…

Constructor chaining

In his article, Dave’s main suggestion for improving object creation performance was to reduce the number of super-classes. When creating class objects, each creation needs to chain the default values and constructors of its ancestor super-classes. The more superclasses we have, the more modular our code can become, but this makes object creation slightly slower. The effect is typically on the order of a few millisecs or less, so unless we have a very long super-class chain or are bulk-creating numerous objects, this has little impact on the overall program performance.

Object pooling

Objects are typically created at the beginning of a program and used throughout it. In such cases, we only pay the small performance penalty once. In cases where objects are constantly being destroyed and created throughout the program’s duration, consider using object pooling to reuse existing objects. The basic idea is to create a set of ready-to-use objects at the beginning of the program. A static (singleton) dispatcher (factory) object would create this pool of objects in its constructor, possibly with a few new objects ready for use. The factory’s only public interface would be public pull/recycle methods to retrieve and return objects from/to the pool. The program would have no access to the objects pool (except via these public methods), since the pool is stored in the factory object’s private data.

The pull method would create new objects only when asked to retrieve objects from an empty pool; otherwise it would simply return a reference to one of the unused pool objects (which need to be handle class objects to be reference-able). The recycle method will be used to return objects to the pool, once they have been used, to enable these objects to be reused later (via subsequent pulls).

Object pooling entails programming overheads that only make sense when a large number of short-lived objects are constantly being created and destroyed, or when object creation is especially expensive. It is often used in databases (connection pooling), since programs often connect to a database numerous times for short-lived SQL queries. Similar ideas can be found in GUI and I/O programming.

Here is a simple implementation of such a system. The singleton factory class is Widgets and it holds a pool of reusable Widget objects:

% Manage a persistent, global, singleton list of Widget objects
classdef Widgets < handle
    properties (Access=private)
        UnusedWidgets@Widget   % only accept elements of type Widget
    end
 
    methods (Access=private)
        % Guard the constructor against external invocation.
        % We only want to allow a single instance of this class
        % This is ensured by calling the constructor from the static (non-class) getInstance() function
        function obj = Widgets()
            % Initialize an initial set of Widget objects
            for idx = 1 : 5
                try
                    obj.UnusedWidgets(idx) = Widget;
                catch
                    obj.UnusedWidgets = Widget;  % case of idx==1
                end
            end
        end
    end
 
    methods (Static)  % Access=public
        % Get a reference to an unused or new widget
        function widget = pull()
            obj = getInstance();
            try
                % Try to return a widget from the list of UnusedWidgets
                widget = obj.UnusedWidgets(end);
                obj.UnusedWidgets(end) = [];  % remove from list
            catch
                widget = Widget;  % create a new Widget object
            end
        end
 
        % Return a widget to the unused pool, once we are done with it
        function recycle(widget)
            obj = getInstance();
            obj.UnusedWidgets(end+1) = widget;
        end
    end
end
 
% Concrete singleton implementation
% Note: this is deliberately placed *outside* the class, so that it is not accessible to the user
function obj = getInstance()
    persistent uniqueInstance
    if isempty(uniqueInstance)
        obj = Widgets();
        uniqueInstance = obj;
    else
        obj = uniqueInstance;
    end
end

We can access and use the Widgets object pool as follows:

% Retrieve a Widget object instance from the pool of objects, or create a new instance as needed
widget = Widgets.pull();
 
% Now use this widget object until we're done with it
 
% Return the object to the pool, for possible later reuse
Widgets.recycle(widget);

Credit: inspired by Bobby Nedelkovski’s Singleton class implementation

Handle vs. value class objects

Another consideration when designing classes is that while handle classes are slightly slower to create (due to multiple super-class overheads), they are typically much faster to use. The reason is that handle classes are passed to functions by reference, whereas value classes are passes by value. Whenever we modify a handle class property within a function, we directly manipulate the relevant property memory. On the other hand, when we manipulate a value class property, a copy of the class needs to be created and then the modified class needs to be copied back to the original object’s memory (using Matlab’s Copy-on-Write mechanism). Since we cannot normally anticipate all usage patterns of a class when we create it, I suggest to create any new user class as handle class, unless there is a specific good reason to make it a value class. All it takes is to add the handle (or hgsetget) inheritance to the class definition:

classdef MyClass < handle

Preallocation

When implicit expansion of class-object arrays takes place, an abbreviated version of object instance creation takes place, which bypasses the constructor calls and just copies the instance properties. For example, array(9)=Widget creates an array of 9 separate Widget objects, but the Widget constructor is only called for array(1) and array(9); array(1) is then expanded (copied-over) to the remaining objects array(2:8).

When preallocating, ensure that you are using the maximal expected array size. There is no point in preallocating an empty array or an array having a smaller size than the expected maximum, since dynamic memory reallocation will automatically kick-in within the processing-loop. For this reason, avoid using the empty() method of class objects to preallocate – use repmat instead (ref).

When using repmat to replicate class objects, always be careful to note whether you are replicating the object itself (this happens if your class does NOT derive from handle) or its reference handle (which happens if you derive the class from handle). If you are replicating objects, then you can safely edit any of their properties independently of each other; but if you replicate references, you are merely using multiple copies of the same reference, so that modifying referenced object #1 will also automatically affect all the other referenced objects. This may or may not be suitable for your particular program requirements, so be careful to check carefully. If you actually need to use independent object copies, you will need to call the class constructor multiple times, once for each new independent object (ref).

Preallocation of class objects (class instances) can be used not just as a means of avoiding dynamic allocation, but also as a means of controlling the time of object initialization. Object initialization, typically done in the class’s constructor method, could be lengthy (depending on your specific application). By preallocating the object, we can control the exact timing in which this initialization occurs, possibly at such a time that is less time-critical in the regular application time-flow. This relates to the concepts of lazy initialization, a special case of deferred (or demand-driven) evaluation.

For additional aspects of preallocation performance, refer to my article from last year, which discusses class objects only briefly, but expands on the other data types.

London training course – March 2014

If you are interested in improving your Matlab application’s performance and your Matlab programming skills in general, join my upcoming Advanced Matlab Programming course in London, March 2014 – an intensive 2 day course on best practices, preparing professional reports and performance tuning. I guarantee that following this course your Matlab programming skills will be at a higher level.

Categories: Low risk of breaking in future versions, Stock Matlab function

Tags: , ,

Bookmark and SharePrint Print

8 Responses to Class object creation performance

  1. Andrew says:

    Interesting use of The MathWorks calls “class-related” functions. Is there a reason that you didn’t make getInstance() an (Access=private,Static,Hidden) method instead? Also, is there a reason for not making Widgets a (Sealed) class to prevent subclassing?

    • @Andrew – I could indeed make getInstance() Static+private (no need to make it hidden), but then I’d need to call it via the Widgets. prefix in pull()/recycle():

      obj = Widgets.getInstance();

      There’s also the small matter of performance – I believe that calling a sub-function as in my implementation is slightly faster than a class method invocation.

      Not major reasons, I admit. There’s a lot of personal taste that goes into such implementations, and there are of course other good implementations. I’ve lost count of the number of singleton implementations I’ve seen over the years. It’s one of those classes that every newbie gets to program as an exercise, and doesn’t know that he got it wrong until he’s shown his/her errors in a technical job interview. Hopefully, my implementation doesn’t fall into this latter category…

      As for not making Widgets Sealed, I see no reason to prevent sub-classes from reimplementing the pool differently. For example, instead of retrieving the last-recycled object, retrieving the oldest one in the pool.

  2. MatMan says:

    You could have mentioned that since Matlab 2011a (I think) you can copy handle classes without invoking the constructor: http://www.mathworks.com/help/matlab/ref/matlab.mixin.copyableclass.html

    Instead of a pool of objects I store only one object and create as many copies as I need when I need them. I haven’t analyzed if a factory approach would be faster though…

  3. Matt Whitaker says:

    Hi Yair,
    A note on the preallocation. If you have a parameter passed in on your class the automatically expanding classes will be called with a parameter constructor

    So if your Widgets class had a varargin input for example then when you try

    array(9)=Widgets('param');

    will call the constructor passing ‘param’ in varargin{1} for the first constructor but then passes in an empty varargin for the next
    So the moral I think is to have a no-parameter constructor defined if you are going to use this.

    At least that’s the way it seems to work on my R2012b
    Cheers

    Matt

  4. Pingback: Accessing private object properties | Undocumented Matlab

Leave a Reply

Your email address will not be published. Required fields are marked *