Performance: accessing handle properties

Graphic handle properties

There are several ways to access (read or update) Handle Graphics object properties. The simplest (and documented) way is to use the built-in get and set functions on the HG object’s handle. However, it is not the fastest: a significant speedup is possible (see below).

Accessing individual properties is so fast that this speedup may not seem important. Individual properties are accessed in tens or hundreds of microseconds, which is a very short time for most Matlab programs. Indeed, if our application seldom accesses properties, it is probably not worth the effort to optimize this particular aspect of the program:

% Individual property access is extremely fast:
hFig = figure;
tic, figName = get(hFig,'name'); toc  % Elapsed time is 0.000229 seconds. 
 
tic, set(hFig,'name','testing'); toc  % Elapsed time is 0.000270 seconds.

But if we have thousands of reads and/or updates, this could possibly become an important factor (drawnow calls are intentionally omitted to illustrate the effect):

% Using the standard set() function
tic, for idx=1:10000, set(hFig,'name','testing'); end, toc  % Elapsed time is 0.229772 seconds. 
 
% Using the HG handle() wrapper is about 25% faster (or 1.3x speedup):
tic
hFig = handle(hFig);
for idx=1:10000, set(hFig,'name','testing'); end
toc  % Elapsed time is 0.170205 seconds. 
 
% Using the HG handle() wrapper with dot notation is even faster (65% faster, or 2.7x speedup):
tic
hFig = handle(hFig);
for idx=1:10000, hFig.name='testing'; end
toc  % Elapsed time is 0.083762 seconds.

Similar results occur when trying to get() a property value:

% Using the standard set() function
tic, for idx=1:10000, s=get(hFig,'name'); end, toc   % Elapsed time is 0.140865 seconds.
 
% Using the HG handle() wrapper is about 25% faster (or 1.3x speedup):
tic, hFig=handle(hFig); for idx=1:10000, s=get(hFig,'name'); end, toc  % Elapsed time is 0.108543 seconds.
 
% Using the HG handle() wrapper with dot notation is even faster (62% faster, or 2.6x speedup):
tic, hFig=handle(hFig); for idx=1:10000, s=hFig.name; end, toc   % Elapsed time is 0.053423 seconds.

We learn from this that using the HG handle() wrapper function is useful for improving performance. This has the benefit of improving our code performance with minimal changes to our code, by simply updating our handle to be a handle() wrapper:

hFig = handle(hFig);

Using the handle’d handle enables further performance benefits if we use the dot notation (handle.propertyName), rather than use the familiar get/set functions.

Note that both the handle function and its beneficial effects on performance are undocumented.

Java reference properties

The same conclusions also hold true for Java objects, where it turns out that using handle() and the simple dot notation is 2-6 times as fast as using the Java set<PropName> and get<PropName> functions, due to Matlab-Java interface aspects:

jb = javax.swing.JButton;
 
% Using the standard set() function
tic, for idx=1:10000, set(jb,'Text','testing'); end, toc  % Elapsed time is 0.278516 seconds.
 
% Using the HG handle() wrapper is about 35% faster (or 1.6x speedup):
jhb = handle(jb);
tic, for idx=1:10000, set(jhb,'Text','testing'); end, toc   % Elapsed time is 0.175018 seconds.
 
% Using the HG handle() wrapper with dot notation is even faster (65% faster, or 2.8x speedup):
tic, for idx=1:10000, jhb.text='testing'; end, toc  % Elapsed time is 0.100239 seconds.
 
% Using the Java setText() function, is actually slower (faster with the handle() wrapper, but still slower than dot-notation):
tic, for idx=1:10000, jb.setText('testing'); end, toc  % Elapsed time is 0.587543 seconds.
tic, for idx=1:10000, jhb.setText('testing'); end, toc  % Elapsed time is 0.201635 seconds.

The same holds true also for retrieving property values via the get function.

User handle class properties

Exactly the same conclusions also apply to non-graphical user classes that derive from the base handle class regarding property access. In this case, we derive the hgsetget built-in class, which provides a handle class with the standard get/set functions. Note that this is documented – it is only the performance implications that are undocumented in this case.

We first create a simple class for testing:

% Define a simple handle class
classdef TestClass < hgsetget 
    properties
        name
    end
end

Now let’s test both sides of the property access (get/set) – first let’s set the property value:

obj = TestClass;
 
% Using the standard set() function
tic, for idx=1:10000, set(obj,'name','testing'); end, toc  % Elapsed time is 0.138276 seconds.
 
% Using class.propName notation - 72x faster!
tic, for idx=1:10000, obj.name='testing'; end, toc  % Elapsed time is 0.001906 seconds.

And similarly for retrieving a property value:

% Using the standard set() function
tic, for idx=1:10000, a=get(obj,'name'); end, toc  % Elapsed time is 0.105168 seconds.
 
% Using class.propName notation - 6.5x faster
tic, for idx=1:10000, a=obj.name; end, toc  % Elapsed time is 0.016179 seconds.

Conclusions

The general conclusion is that we should always strive to use the dot notation (handle.propertyName), rather than use the familiar get/set functions, in performance hotspots. Naturally, doing this in non-hotspot locations is futile, since accessing individual properties is relatively fast.

In the upcoming HG2, when all of Matlab’s GUI and graphic objects will use Matlab classes, this conclusion will be more important than ever. Initial tests on the HG2 alpha (that anyone can access) show that they hold true for HG2 just as they do in the current HG1.

The handle() function wrapper produces a UDD (schema) object, so while I have not specifically tested non-HG schema objects, I would be very surprised if the conclusions do not hold true for all UDD objects in general.

I have also not [yet] tested other types of handles (ActiveX/COM, Dot-Net etc.) but again, I would be very surprised if the conclusions would be any different.

If this performance tip has piqued your interest, then you might be interested in the several hundred other tips in my upcoming book “MATLAB Performance Tuning” (CRC Press, 2014). I’ll post a note on this blog letting everyone know when it’s officially available. In the meantime, enjoy my other performance-related articles – there are already several dozen of them by now, and the list will continue to grow…

Editorial note: In the past month I’ve gone to quite a lot of effort to improve the performance of this website. Most users should now see pages loading about twice as fast as one month ago. The effect should be especially noticeable on mobile devices connected over mobile, which is much slower than wifi/cable. I still have a few additional tweak ideas, so I expect performance to slightly improve even further in the upcoming weeks. Enjoy reading!

Categories: GUI, Handle graphics, Low risk of breaking in future versions, Stock Matlab function, Undocumented feature, Undocumented function

Tags: , , , , , ,

Bookmark and SharePrint Print

13 Responses to Performance: accessing handle properties

  1. oro77 says:

    Thank you very much, I did not know about the handle() tip. It will increase the performance of my GUI.
    I have a question concerning the java Button. How do you put it in the figure ? There is no parent member in the handle. How to change the size of the button using the java button ?

    jb = javax.swing.JButton;
  2. Lucius Domitius Ahenobarbus says:

    Thanks a lot! there is another thing, which makes this even more interesting for me: With the dot-notation, one can use the autocomplete-functionality :) what makes coding a bit faster for me in many cases.

  3. Dan says:

    Yair, I noticed something interesting. It appears that you pay the time penalty the first time you access any property within a given scope. Try this: create a handle object and pass it into a function (you can pass it in within a cell array, at a minimum). The first time you access any property within the function you pay the time penalty, but all subsequent calls are fast. BUT: you pay that penalty every time you call the function… Try profiling this:

    function SpeedTests
       fig = figure;
       for i = 1:4
          hObj(i) = subplot(2,2,i);
          handleObj{i} = handle(hObj(i));
          handleObjs2(i) = handle(hObj(i));
       end
       for itest = 1:1000
          subfun(handleObj,hObj,handleObjs2);
       end
       return
    end
     
    function subfun(handleObj,hObj,handleObjs2)
       for i = 1:4
          pos0 = handleObjs2(i).Position;
          pos1 = handleObj{i}.Position;
          pos2 = get(hObj(i),'Position');
          pos3 = handleObj{i}.Position;
          XLIMS = handleObj{i}.XLim;
       end
    end

    Any thoughts?

  4. Malcolm Lidierth says:

    While this may not be a real application test case note that using GShell (which uses a Groovy shell) from Waterloo in MATLAB gives:

    tic;
    GShell.setVariable('button', jhb);
    GShell.eval('for (def idx=0; idx < 10000; idx++) { button.setText("testing")'); 
    toc;

    Elapsed time is 0.019272 seconds.

    A ~10x improvement on the best of the above on my machine (after GShell warm up).

    Further info at http://waterloo.sourceforge.net/groovy/GShell.html

    Horses-for-courses: expect JVM languages to outperform MATLAB on JVM operations ; and MATLAB to (substantially) outperform the JVM when working with natives.

    • Malcolm Lidierth says:

      PPS. Adding the code to a script file and using run allows the compiled code to be used each time. Then we see

      >> GShell.load('/Users/ML/blogtest.groovy')  % Load and compile once
      ans =
           1
       
      >> tic;GShell.run();toc
      Elapsed time is 0.060009 seconds.
      >> tic;GShell.run();toc
      Elapsed time is 0.019712 seconds.
      >> tic;GShell.run();toc
      Elapsed time is 0.003378 seconds.
      >> tic;GShell.run();toc
      Elapsed time is 0.003129 seconds.
      >> tic;GShell.run();toc
      Elapsed time is 0.003126 seconds.
      >> tic;GShell.run();toc
      Elapsed time is 0.003531 seconds.
      >> tic;GShell.run();toc
      Elapsed time is 0.003365 seconds.
      >> tic;GShell.run();toc
      Elapsed time is 0.003394 seconds.

      The performance improvements are down to HotSpot I guess.

      As this is a JButton, the code should really be run on the EDT.
      Using GShell.runEDT – which uses invokeAndWait – performance returns to ~0.03s.
      Using GShell.runLater – which uses invokeLater – performance improves another 10x (the MATLAB thread is not blocked as control returns as soon as the code is posted to the EDT).

      invokeAndWait cannot be called if already on the EDT (e.g. if javaObjectEDT has been used on the button). GShell solves that with GShell.runQuery which uses GShell.runEDT if it can, and GShell.runLater otherwise.

      Note that GShell is independent of the rest of the Waterloo code – in fact it’s wrapped in an RStudio project folder and can be used from MATLAB, R, Python, SciLab, JRuby etc. It can be used to run any Java/Groovy code and control threading e.g. to create a background SwingWorker thread or work on the JavaFX platform thread.

      GShell contains little or no novel code – it’s just giving easy access to a Groovy shell instance from other scripting environments.

  5. Xiaohu says:

    Nice tip (as always)!
    It is however surprising to me that using dot notation is actually faster. As you probably know, method invocation using function notation is typically faster in Matlab, see:
    http://blogs.mathworks.com/loren/2012/03/26/considering-performance-in-object-oriented-matlab-code/#11
    http://stackoverflow.com/questions/1693429/is-matlab-oop-slow-or-am-i-doing-something-wrong
    Any insight on this?

    • @Xiaohu – I do not understand why this is, but it appears to be consistent. If anyone has an explanation, I’d be happy to learn.

    • Andrew says:

      This is not really the case of two different ways of invoking a method. We’re setting or getting a property. Properties can either be directly specified/obtained using set/get methods (allowing for things like validation) or via “dot” notation. The latter is much simpler and probably why this is faster. Indeed, trying to get nonexistent properties using the various methods shows that they return different error messages. When setting nonexistent properties, the set method returns an error, but the “dot” notation methods obviously do not. This additional overhead of validation may be what is slowing down get/set over repeated calls.

    • @Andrew – you may indeed be correct, but when we use a.B=c notation, the setB(a,c) function is still getting invoked if you declare it, which seems to indicate that the opposite is the case. But then it would not explain why a.B would be faster than calling setB() directly. I don’t have a good answer to explain the apparent contradiction.

  6. Bjorn says:

    This seems to be a good approach when resetting the same handle over and over again. Not quite the case when updating many different graphical objects.

    %Setup many axes in one figure
    tmp = figure;
    Fig = zeros(1,10000);
    for idx=1:10000
        Fig(idx) = axes('parent',tmp);
    end
     
    % Using standard set() function
    tic, for idx=1:10000, set(Fig(idx),'tag','testing'); end, toc % Elapsed time is 0.163880 seconds
     
    % Using the HG handle() wrapper within the loop
    tic, for idx=1:10000, hFig = handle(Fig(idx)); set(hFig,'tag','testing'); end, toc % Elapsed time is 0.375456 seconds
     
    % Using the HG handle() wrapper outside of the loop
    tic
    hFig = handle(Fig);
    for idx=1:10000, set(hFig(idx),'tag','testing'); end, 
    toc % Elapsed time is 0.241068 seconds
     
    % Using dot notation with handle() wrapper within the loop
    tic
    for idx=1:10000, hFig = handle(Fig(idx)); hFig.tag = 'testing'; end
    toc % Elapsed time is 0.290306 seconds
     
    % Using dot notation with handle() wrapper outside of the loop
    tic
    hFig = handle(Fig);
    for idx=1:10000, hFig(idx).tag = 'testing'; end
    toc % Elapsed time is 3.585026 seconds

Leave a Reply


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