Quantcast
Channel: Undocumented Matlab
Viewing all articles
Browse latest Browse all 219

Callback functions performance

$
0
0

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

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

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

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. Controlling callback re-entrancy Callback reentrancy is a major problem for frequently-fired events. Luckily, it can easily be solved....
  2. 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....
  3. uicontextmenu performance Matlab uicontextmenus are not automatically deleted with their associated objects, leading to leaks and slow-downs. ...
  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....
 

Viewing all articles
Browse latest Browse all 219

Trending Articles