Undocumented Matlab
  • SERVICES
    • Consulting
    • Development
    • Training
    • Gallery
    • Testimonials
  • PRODUCTS
    • IQML: IQFeed-Matlab connector
    • IB-Matlab: InteractiveBrokers-Matlab connector
    • EODML: EODHistoricalData-Matlab connector
    • Webinars
  • BOOKS
    • Secrets of MATLAB-Java Programming
    • Accelerating MATLAB Performance
    • MATLAB Succinctly
  • ARTICLES
  • ABOUT
    • Policies
  • CONTACT
  • SERVICES
    • Consulting
    • Development
    • Training
    • Gallery
    • Testimonials
  • PRODUCTS
    • IQML: IQFeed-Matlab connector
    • IB-Matlab: InteractiveBrokers-Matlab connector
    • EODML: EODHistoricalData-Matlab connector
    • Webinars
  • BOOKS
    • Secrets of MATLAB-Java Programming
    • Accelerating MATLAB Performance
    • MATLAB Succinctly
  • ARTICLES
  • ABOUT
    • Policies
  • CONTACT

Callback functions performance

September 9, 2015 12 Comments

Matlab enables a variety of ways to define callbacks for asynchronous events (such as interactive GUI actions or timer invocations). We can provide a function handle, a cell-array (of function handle and extra parameters), and in some cases also a string that will be eval‘ed in run-time. For example:

hButton = uicontrol(..., 'Callback', @myCallbackFunc);  % function handle
hButton = uicontrol(..., 'Callback', {@myCallbackFunc,data1,data2});  % cell-array
hButton = uicontrol(..., 'Callback', 'disp clicked!');  % string to eval

hButton = uicontrol(..., 'Callback', @myCallbackFunc); % function handle hButton = uicontrol(..., 'Callback', {@myCallbackFunc,data1,data2}); % cell-array hButton = uicontrol(..., 'Callback', 'disp clicked!'); % string to eval

The first format, function handle, is by far the most common in Matlab code. This format has two variant: we can specify the direct handle to the function (as in @myCallbackFunc), or we could use an anonymous function, like this:

hButton = uicontrol(..., 'Callback', @(h,e) myCallbackFunc(h,e));  % anonymous function handle

hButton = uicontrol(..., 'Callback', @(h,e) myCallbackFunc(h,e)); % anonymous function handle

All Matlab callbacks accept two input args by default: the control’s handle (hButton in this example), and a struct or object that contain the event’s data in internal fields. In our anonymous function variant, we therefore defined a function that accepts two input args (h,e) and calls myCallbackFunc(h,e).
These two variants are functionally equivalent:

hButton = uicontrol(..., 'Callback', @myCallbackFunc);             % direct function handle
hButton = uicontrol(..., 'Callback', @(h,e) myCallbackFunc(h,e));  % anonymous function handle

hButton = uicontrol(..., 'Callback', @myCallbackFunc); % direct function handle hButton = uicontrol(..., 'Callback', @(h,e) myCallbackFunc(h,e)); % anonymous function handle

In my experience, the anonymous function variant is widely used – I see it extensively in many of my consulting clients’ code. Unfortunately, there could be a huge performance penalty when using this variant compared to a direct function handle, which many people are simply not aware of. I believe that even many MathWorkers are not aware of this, based on a recent conversation I’ve had with someone in the know, as well as from the numerous usage examples in internal Matlab code: see the screenshot below for some examples; there are numerous others scattered throughout the Matlab code corpus.
Part of the reason for this penalty not being well known may be that Matlab’s Profiler does not directly attribute the overheads. Here is a typical screenshot:

Profiling anonymous callback function performance
Profiling anonymous callback function performance

In this example, a heavily-laden GUI figure window was closed, triggering multiple cleanup callbacks, most of them belonging to internal Matlab code. Closing the figure took a whopping 8 secs. As can be seen from the screenshot, the callbacks themselves only take ~0.66 secs, and an additional 7.4 secs (92% of the total) is unattributed to any specific line. Think about it for a moment: we can only really see what’s happening in 8% of the time – the Profiler provides no clue about the root cause of the remaining 92%.
The solution in this case was to notice that the callback was defined using an anonymous function, @(h,e)obj.tableDeletedCallbackFcn(e). Changing all such instances to @obj.tableDeletedCallbackFcn (the function interface naturally needed to change to accept h as the first input arg) drastically cut the processing time, since direct function handles do not carry the same performance overheads as anonymous functions. In this specific example, closing the figure window now became almost instantaneous (<1 sec).

Conclusions

There are several morals that I think can be gained from this:

  1. When we see unattributed time in the Profiler summary report, odds are high that this is due to function-call overheads. MathWorks have significantly reduced such overheads in the new R2015b (released last week), but anonymous [and to some degree also class methods] functions still carry a non-negligible invocation overheads that should be avoided if possible, by using direct [possibly non-MCOS] functions.
  2. Use direct function handles rather than anonymous function handles, wherever possible
  3. In the future, MathWorks will hopefully improve Matlab’s new engine (“LXE”) to automatically identify cases of @(h,e)func(h,e) and replace them with faster calls to @func, but in any case it would be wise to manually make this change in our code today. It would immediately improve readability, maintainability and performance, while still being entirely future-compatible.
  4. In the future, MathWorks may also possibly improve the overheads of anonymous function invocations. This is more tricky than the straight-forward lexical substitution above, because anonymous functions need to carry the run-time workspace with them. This is a little known and certainly very little-used fact, which means that in practice most usage patterns of anonymous functions can be statically analyzed and converted into much faster direct function handles that carry no run-time workspace info. This is indeed tricky, but it could directly improve performance of many Matlab programs that naively use anonymous functions.
  5. Matlab’s Profiler should really be improved to provide more information about unattributed time spent in internal Matlab code, to provide users clues that would help them reduce it. Some information could be gained by using the Profiler’s -detail builtin input args (which was documented until several releases ago, but then apparently became unsupported). I think that the Profiler should still be made to provide better insights in such cases.

Oh, and did I mention already the nice work MathWorks did with 15b’s LXE? Matlab’s JIT replacement was many years in the making, possibly since the mid 2000’s. We now see just the tip of the iceberg of this new engine: I hope that additional benefits will become apparent in future releases.
For a definitive benchmark of Matlab’s function-call overheads in various variants, readers are referred to Andrew Janke’s excellent utility (with some pre-15b usage results and analysis). Running this benchmark on my machine shows significant overhead reduction in function-call overheads in 15b in many (but not all) invocation types.
For those people wondering, 15b’s LXE does improve HG2’s performance, but just by a small bit – still not enough to offset the large performance hit of HG2 vs. HG1 in several key aspects. MathWorks is actively working to improve HG2’s performance, but unfortunately there is still no breakthrough as of 15b.
Additional details on various performance issues related to Matlab function calls (and graphics and anything else in Matlab) can be found in my recent book, Accelerating MATLAB Performance.

Related posts:

  1. Matlab-Java memory leaks, performance – Internal fields of Java objects may leak memory - this article explains how to avoid this without sacrificing performance. ...
  2. Controlling callback re-entrancy – Callback reentrancy is a major problem for frequently-fired events. Luckily, it can easily be solved....
  3. Continuous slider callback – Matlab slider uicontrols do not enable a continuous-motion callback by default. This article explains how this can be achieved using undocumented features....
  4. Undocumented mouse pointer functions – Matlab contains several well-documented functions and properties for the mouse pointer. However, some very-useful functions have remained undocumented and unsupported. This post details their usage....
  5. Speeding-up builtin Matlab functions – part 2 – Built-in Matlab functions can often be profiled and optimized for improved run-time performance. This article shows a typical example. ...
  6. Speeding-up builtin Matlab functions – part 1 – Built-in Matlab functions can often be profiled and optimized for improved run-time performance. This article shows a typical example. ...
Callbacks GUI Handle graphics JIT LXE Performance Pure Matlab
Print Print
« Previous
Next »
12 Responses
  1. C. Chu September 15, 2015 at 13:54 Reply

    In your example, you used @obj.tableDeletedCallbackFcn instead of @(h,e) obj.tableDeletedCallbackFcn. But elsewhere on the site; in your MATLAB Performance book; and on the SO page you showed, it’s faster to do, for instance, tableDeletedCallbackFcn(obj).

    So I have an awful lot of code where I set up callbacks as such:

    handle.Callback = @(h,e) myCallback(obj, h, e)

    handle.Callback = @(h,e) myCallback(obj, h, e)

    So would you actually get better performance this time using obj.myCallback instead of myCallback(obj)?

    • Yair Altman September 16, 2015 at 06:31 Reply

      @Clayton – when directly invoking a function, obj.tableDeletedCallbackFcn is indeed typically slower than tableDeletedCallbackFcn(obj). However, with callbacks you must specify a function handle and so the only alternatives are to either use @obj.tableDeletedCallbackFcn or an anonymous function (which is much slower). Anonymous functions, as of R2015b, are an order of magnitude slower than any other function invocation alternative.

      In summary, @obj.myCallback is equivalent to, but faster, simpler and more maintainable than @(h,e) myCallback(obj, h, e).

  2. Martin Afanasjew September 16, 2015 at 08:30 Reply

    Note also that there’s a subtle difference between obj.myCallback() and myCallback(obj). The former explicitly states that a method of obj should be called (MATLAB won’t search anywhere else to find a myCallback). In contrast, the latter version could call a regular function or a method of obj. So I wouldn’t recommend switching between them blindly, even if the result is very likely to be the same.

    If multiple arguments were involved, like in myCallback(a, b, c), and arguments had different type, there would be even more possibilities for what gets called. E.g., if c had a higher precedence than the other two arguments, then the method from the class of c would be picked, if available. (See the InferiorClasses attribute used with classdef and also “Class Precedence” in the MATLAB documentation. The concept is called multiple dispatch, which is not a very common programming language feature, but makes a lot of sense in an environment geared towards scientific computing.)

    These subtleties are probably not very relevant in the context of callbacks, but it is good to be aware of them nonetheless.

    • Martin Afanasjew September 16, 2015 at 08:59 Reply

      One more note about the performance implications of changing @(h, e) obj.tableDeletedCallbackFcn(e) to @obj.tableDeletedCallbackFcn (example from the article):

      I fail to see why this should improve performance at all. My doubts stem from the fact that the latter looks like a simple function handle, but in fact turns out to be an anonymous function handle. MATLAB R2015b prints: @(varargin)obj.tableDeletedCallbackFcn(varargin{:}). So this only saves us from having to write out the arguments. And calling functions (a function that offers a glimpse at the internal representation of function handles) on that also confirms the type to be “anonymous”. From a conceptual point of view it has to be that way, because anonymous function handles are the only type capable of storing some workspace alongside the function handle. And, well, there’s this obj that needs to be stored somewhere.

      I have to admit, that I haven’t benchmarked theses different versions, though. So maybe structurally there should be no big difference, but in fact there is.

    • Martin Afanasjew September 27, 2015 at 04:49 Reply

      I finally ran a simple benchmark (on OS X 10.10.5) to support my argument. It also very nicely shows the performance improvements in R2015b.

      In the benchmark obj is an instance of a custom polynomial class and x is a double scalar. The work done in the plus method is minor, so the performance is dominated by the function/method call overhead. Every run consists of a thousand invocations of the “function handle” created before the loop.

      Results after 10 runs (in 84.13s) on MATLAB R2015a:

      | Variant                  | Average |    Best |   Worst |
      | @(x) obj.plus(x)         |   2.08s |   2.04s |   2.31s |
      | @obj.plus                |   2.09s |   2.05s |   2.24s |
      | @(x) plus(obj, x)        |   1.72s |   1.70s |   1.75s |
      | MethodHandle(obj, @plus) |   1.76s |   1.73s |   1.80s |
      

      Results after 10 runs (in 47.81s) on MATLAB R2015b:

      | Variant                  | Average |    Best |   Worst |
      | @(x) obj.plus(x)         |   1.28s |   1.26s |   1.29s |
      | @obj.plus                |   1.29s |   1.28s |   1.33s |
      | @(x) plus(obj, x)        |   0.86s |   0.84s |   0.89s |
      | MethodHandle(obj, @plus) |   0.91s |   0.90s |   0.92s |
      

      The last line was initially a silly experiment, that turned out to be surprisingly fast. It is a class that holds an object instance and a function handle and overrides the subsref method to provide a function-handle-like interface. In the end MethodHandle(obj, @method) should behave like @(varargin) method(obj, varargin{:}).

      • Yair Altman September 27, 2015 at 04:55

        You are missing one important variant, that I expect to be the fastest of all: @plus where plus(obj,x) is a sub-function in the same class file as the main class, but implemented as a separate sub-function rather than a class method. This avoids the two most important method-invocation performance hotspots: anonymous functions and class methods. For any performance-critical code that is repeatedly invoked in class methods, I often use such sub-functions (or standalone procedural functions).

    • Martin Afanasjew September 27, 2015 at 06:34 Reply

      @Yair – there are probably many variants I haven’t tested. If I have a function used internally by a single class, then I fully agree with you. However, the exercise here was to create a function handle that forwards its arguments to a method of a class while passing an instance of that class as the first argument. And I fail to see how your suggestion could be used in this scenario, or am I misunderstanding you?

  3. Chris Guenther September 17, 2015 at 10:05 Reply

    I’m using GUIDE for creating GUIs, and by default they do this for callbacks:

    @(hObject,eventdata)Main_Program('button_name_Callback',hObject,eventdata,guidata(hObject))

    @(hObject,eventdata)Main_Program('button_name_Callback',hObject,eventdata,guidata(hObject))

    I tried changing this to @button_name_Callback, but it seems that where it’s evaluating the callbacks, functions like button_name_Callback inside Main_Program are out of scope. Is this a limitation of GUIDE, or do you know if there some way I can make this work?

    • Yair Altman September 17, 2015 at 10:11 Reply

      @Chris – this is a limitation of GUIDE. You can modify the callbacks programmatically in your GUIDE-generated m-file (Main_Program.m), for example in the OpeningFcn(). In this function, all the other subfunctions of the m-file are in scope and so you can directly set the relevant callbacks to their direct function handles.

  4. Alex December 7, 2016 at 12:52 Reply

    I’ve tried to adapt the function callback for my GUI designed with GUIDE. I’ve adapted

    @(hObject,eventdata)CAEQE('testListBox1_Callback',hObject,eventdata,guidata(hObject))

    @(hObject,eventdata)CAEQE('testListBox1_Callback',hObject,eventdata,guidata(hObject))

    to

    @testListBox1_Callback

    @testListBox1_Callback

    inside of my main code.
    But in my case it won’t work to get the Callback faster. Can you help?

    Alex

    • Yair Altman December 7, 2016 at 16:18 Reply

      @Alex – there is no immediate answer. If you want me to look at your specific code, then email me (altmany at gmail) for a dedicated consulting session.

  5. Jan K September 26, 2019 at 13:07 Reply

    While the obj.function vs function(obj) times are almost identical for Matlab code (see i.e. the discussion here: https://stackoverflow.com/a/1745686/3620376), it’s not solved for called .NET (C#) code. The results were quite clear, in my case 35.57 seconds (Net 4.5 obj.nop()) vs 8.72 seconds (Net 4.5 nop(obj)).
    I’ve made an update to Andrew Janke’s repo at https://github.com/apjanke/matlab-bench to test that.
    .

    Even more interesting is the fact, that a event callback, thrown in .NET and caught in Matlab is slower when it’s an pure function handle. It’s marginal but visible and contrary to your example above, where you state, that all anonymous function calls should be replaced by non-anonymous ones if possible.
    .

    ‘DataIsReady’ is a .NET Framework 4.5 (C#) event. I’m at Matlab 2019b @ windows 10.

    % somewhere in a handle class
    lis = addlistener(evnt, 'DataIsReady', @obj.onSerialBytesAvailableNet);
     
    ...
    function onSerialBytesAvailableNet(obj, src, evnt)
         obj.cnt = obj.cnt + 1;
    end

    % somewhere in a handle class lis = addlistener(evnt, 'DataIsReady', @obj.onSerialBytesAvailableNet); ... function onSerialBytesAvailableNet(obj, src, evnt) obj.cnt = obj.cnt + 1; end

    vs

    % somewhere in a handle class
    lis = addlistener(evnt, 'DataIsReady', @(src,evnt)obj.onSerialBytesAvailableNet(src,evnt)); % faster than the rest!
    ...
    function onSerialBytesAvailableNet(obj, src, evnt)
         obj.cnt = obj.cnt + 1;
    end

    % somewhere in a handle class lis = addlistener(evnt, 'DataIsReady', @(src,evnt)obj.onSerialBytesAvailableNet(src,evnt)); % faster than the rest! ... function onSerialBytesAvailableNet(obj, src, evnt) obj.cnt = obj.cnt + 1; end

    Profiler times:
    …j.onSerialBytesAvailableNet(src,evnt) 40807 8.369 s 0.220 s
    …nSerialBytesAvailableNet(varargin{:}) 38265 8.407 s 0.347 s

    Looks like the addlistener calls the function with varargin{:} when a non-anonymous function is used. I don’t know if that’s special for .NET events or default, but it’s more than 50 % slower in my case.
    I know, there is a big number of function calls, and probably not worth mentioning or impacting performance but just figured that out and wanted to hear you comments 🙂

    Thanks

Leave a Reply
HTML tags such as <b> or <i> are accepted.
Wrap code fragments inside <pre lang="matlab"> tags, like this:
<pre lang="matlab">
a = magic(3);
disp(sum(a))
</pre>
I reserve the right to edit/delete comments (read the site policies).
Not all comments will be answered. You can always email me (altmany at gmail) for private consulting.

Click here to cancel reply.

Useful links
  •  Email Yair Altman
  •  Subscribe to new posts (feed)
  •  Subscribe to new posts (reader)
  •  Subscribe to comments (feed)
 
Accelerating MATLAB Performance book
Recent Posts

Speeding-up builtin Matlab functions – part 3

Improving graphics interactivity

Interesting Matlab puzzle – analysis

Interesting Matlab puzzle

Undocumented plot marker types

Matlab toolstrip – part 9 (popup figures)

Matlab toolstrip – part 8 (galleries)

Matlab toolstrip – part 7 (selection controls)

Matlab toolstrip – part 6 (complex controls)

Matlab toolstrip – part 5 (icons)

Matlab toolstrip – part 4 (control customization)

Reverting axes controls in figure toolbar

Matlab toolstrip – part 3 (basic customization)

Matlab toolstrip – part 2 (ToolGroup App)

Matlab toolstrip – part 1

Categories
  • Desktop (45)
  • Figure window (59)
  • Guest bloggers (65)
  • GUI (165)
  • Handle graphics (84)
  • Hidden property (42)
  • Icons (15)
  • Java (174)
  • Listeners (22)
  • Memory (16)
  • Mex (13)
  • Presumed future risk (394)
    • High risk of breaking in future versions (100)
    • Low risk of breaking in future versions (160)
    • Medium risk of breaking in future versions (136)
  • Public presentation (6)
  • Semi-documented feature (10)
  • Semi-documented function (35)
  • Stock Matlab function (140)
  • Toolbox (10)
  • UI controls (52)
  • Uncategorized (13)
  • Undocumented feature (217)
  • Undocumented function (37)
Tags
AppDesigner (9) Callbacks (31) Compiler (10) Desktop (38) Donn Shull (10) Editor (8) Figure (19) FindJObj (27) GUI (141) GUIDE (8) Handle graphics (78) HG2 (34) Hidden property (51) HTML (26) Icons (9) Internal component (39) Java (178) JavaFrame (20) JIDE (19) JMI (8) Listener (17) Malcolm Lidierth (8) MCOS (11) Memory (13) Menubar (9) Mex (14) Optical illusion (11) Performance (78) Profiler (9) Pure Matlab (187) schema (7) schema.class (8) schema.prop (18) Semi-documented feature (6) Semi-documented function (33) Toolbar (14) Toolstrip (13) uicontrol (37) uifigure (8) UIInspect (12) uitable (6) uitools (20) Undocumented feature (187) Undocumented function (37) Undocumented property (20)
Recent Comments
Contact us
Captcha image for Custom Contact Forms plugin. You must type the numbers shown in the image
Undocumented Matlab © 2009 - Yair Altman
This website and Octahedron Ltd. are not affiliated with The MathWorks Inc.; MATLAB® is a registered trademark of The MathWorks Inc.
Scroll to top