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…
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.
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
% 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
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) is then expanded (copied-over) to the remaining objects
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.