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

uicontextmenu performance

$
0
0

I would like to introduce guest blogger Robert Cumming, an independent contractor based in the UK who has recently worked on certification of the newest advanced civil aircraft. Today Robert will discuss the performance of uicontextmenus in interactive GUIs, which were used extensively in flight test analysis.

Have you ever noticed that a GUI slows down over time? I was responsible for designing a highly complex interactive GUI, which plotted flight test data for engineers and designers to analyze data for comparison with pre-flight predictions. This involved extensive plotting of data (pressure, forces/moments, anemometry, actuator settings etc….), where individual data points were required to have specific/customizable uicontextmenus.

Matlab’s documentation on uicontextmenus discusses adding them to plots, but makes no mention of the cleaning up afterwards.

Let’s start with some GUI basics. First we create a figure with a simple axes and a line:

x = [-10:0.2:10];
y = x.^2;
h = figure;
ax = axes ( 'parent',h );
hplot = plot ( ax, x, y );

Adding a uicontextmenu to the plot creates extra objects:

uic = uicontextmenu;
uimenu ( uic, 'Label','Menu A.1' );
set ( hplot, 'uicontextmenu',uic );
fprintf ( 'Figure (h) has %i objects\n', length ( findobj ( h ) ) );

In this instance there are 5 objects, the individual menu and uicontextmenu have created an additional 2 objects. All of this is quite basic as you would expect.

Basic plot with a custom context menu

Basic plot with a custom context menu


We now clear the plot using cla and draw a new line with its own new uicontextmenu. Note that we don’t save the plot handle this time:

cla ( ax );
uic = uicontextmenu;
uimenu ( uic, 'Label','Menu B.1' );
plot ( ax, x, -y , 'uicontextmenu',uic );
fprintf ( 'Figure (h) has %i objects\n', length ( findobj ( h ) ) );

Another basic plot with a new context menu

Another basic plot with a new context menu

This time the fprintf line tells us that the figure has 7 objects. This may not have been expected, as the first plot was cleared and the original context menu is no longer accessible (cla removed the plot and the line object hplot).

Let’s check these object handles:

>> ishandle ( findobj( h ) )'
ans =
     1     1     1     1     1     1     1     1     1     1     1     1     1

We see that all the objects are valid handles. At first this may perhaps appear confusing: after all, the plot with “Menu A.1″ was deleted. Let’s check this:

>> ishandle ( hplot )
ans =
     0
 
>> get(hplot)
Error using handle.handle/get
Invalid or deleted object.

So it appears that although the plot line was indeed deleted, its associated context menu was not. The reason for this is that the context menu is created as a child of the figure window, not the plot. We are simply using the plot line’s UIContextMenu property to allow the user to obtain access to it and its associated menus.

Once we understand this we can do two things:

  1. Use the same uicontextmenu for each plot
  2. Built individual uicontextmenus for each plot, but remember to clean up afterwards

Is this really such a big issue?

You may be wondering how big a problem is this creation of extra objects. The answer is that for simple cases like this it is really not a big issue. But let’s consider a more realistic case where we also assign callbacks to the menus. First we will create a figure with an axes, for plotting on and a uicontrol edit for displaying a message:

function uicontextExample
   h.main = figure;
   h.ax = axes ( 'parent',h.main, 'NextPlot','add', 'position',[0.1 0.2 0.8 0.7] );
   h.msg = uicontrol ( 'style','edit', 'units','normalized', 'position',[0.08 0.01 0.65 0.1], 'backgroundcolor','white' );
   uicontrol ( 'style','pushbutton', 'units','normalized', 'position',[0.75 0.01 0.2 0.1], 'Callback',{@RedrawX20 h}, 'string','re-draw x20' );
   redraw ( [], [], h )  % see below
end

The callback redraw (shown below) draws a simple curve and assigns individual uicontextmenus to each individual item in the plot. Each uicontextmenu has 12 menu items:

function redraw ( obj, event, h, varargin )
   cla(h.ax);
   start = tic;
   x = -50:50;
   y = x.^2;
   for ii = 1 : length(x)
      uim = uicontextmenu ( 'parent',h.main );
      for jj = 1 : 10
         menuLabel = sprintf ( 'Menu %i.%i', ii, jj );
         uimenu ( 'parent',uim, 'Label',menuLabel, 'Callback',{@redraw h} )
      end
      xStr = sprintf ( 'X = %f', x(ii) );
      yStr = sprintf ( 'Y = %f', y(ii) );
      uimenu ( 'parent',uim, 'Label',xStr, 'Callback',{@redraw h} )
      uimenu ( 'parent',uim, 'Label',yStr, 'Callback',{@redraw h} )
      plot ( h.ax, x(ii), y(ii), 'rs', 'uicontextmenu',uim );
   end
   objs = findobj ( h.main );
   s = sprintf ( 'figure contains %i objects - drawn in %3.2f seconds', length(objs), toc(start) );
   set ( h.msg, 'string',s );
   fprintf('%s\n',s)
end

To help demonstrate the slow-down in speed, the pushbutton uicontrol will redraw the plot 20 times, and show the results from the profiler:

function RedrawX20 ( obj, event, h )
   profile on
   set ( obj, 'enable','off' )
   for ii = 1 : 20
      redraw ( [], [], h );
      drawnow();
   end
   set ( obj, 'enable','on' )
   profile viewer
end

The first time we run this, on a reasonable laptop it takes 0.24 seconds to draw the figure with all the menus:

Multiple context menus assigned to individual data points

Multiple context menus assigned to individual data points

When we press the <re-draw x20> button we get:

figure contains 2731 objects - drawn in 0.28 seconds
figure contains 4044 objects - drawn in 0.28 seconds
figure contains 5357 objects - drawn in 0.28 seconds
figure contains 6670 objects - drawn in 0.30 seconds
figure contains 7983 objects - drawn in 0.32 seconds
figure contains 9296 objects - drawn in 0.30 seconds
figure contains 10609 objects - drawn in 0.31 seconds
figure contains 11922 objects - drawn in 0.30 seconds
figure contains 13235 objects - drawn in 0.32 seconds
figure contains 14548 objects - drawn in 0.30 seconds
figure contains 15861 objects - drawn in 0.31 seconds
figure contains 17174 objects - drawn in 0.31 seconds
figure contains 18487 objects - drawn in 0.32 seconds
figure contains 19800 objects - drawn in 0.33 seconds
figure contains 21113 objects - drawn in 0.32 seconds
figure contains 22426 objects - drawn in 0.33 seconds
figure contains 23739 objects - drawn in 0.35 seconds
figure contains 25052 objects - drawn in 0.34 seconds
figure contains 26365 objects - drawn in 0.35 seconds
figure contains 27678 objects - drawn in 0.35 seconds

The run time shows significant degradation, and we have many left-over objects. The profiler output confirms where the time is being spent:

Profiling results - context menu creation is an evident hotspot

Profiling results - context menu creation is an evident hotspot

As expected the menus creation takes most of the time. Note that the timing that you will get on your own computer for this example may vary and the slowdown may be less noticeable.

Let’s extend the example to include extra input arguments in the callback (in this example they are not used – but in the industrial example extra input arguments are very possible, and were required by the customer):

for ii=1:length(x)
   uim = uicontextmenu ( 'parent',h.main );
   for jj = 1 : 10
      menuLabel = sprintf ( 'Menu %i.%i', ii, jj );
      uimenu ( 'parent',uim, 'Label',menuLabel, 'Callback',{@redraw h 1 0 1} )
   end
   xStr = sprintf ( 'X = %f', x(ii) );
   yStr = sprintf ( 'Y = %f', y(ii) );
   uimenu ( 'parent',uim, 'Label',xStr, 'Callback',{@redraw h 1 0 1} )
   uimenu ( 'parent',uim, 'Label',yStr, 'Callback',{@redraw h 1 0 1} )
   plot ( h.ax, x(ii), y(ii), 'rs', 'uicontextmenu',uim );
end

Re-running the code and pressing <re-draw x20> we get:

figure contains 1418 objects - drawn in 0.29 seconds
figure contains 2731 objects - drawn in 0.37 seconds
figure contains 4044 objects - drawn in 0.48 seconds
figure contains 5357 objects - drawn in 0.65 seconds
...
figure contains 23739 objects - drawn in 4.99 seconds
figure contains 25052 objects - drawn in 5.34 seconds
figure contains 26365 objects - drawn in 5.88 seconds
figure contains 27678 objects - drawn in 6.22 seconds

Note that the 6.22 seconds is just the time that it took the last individual redraw, not the total time to draw 20 times (which was just over a minute). The profiler is again used to confirm that the vast majority of the time (57 seconds) was spent in the uimenu calls, mostly on line #23. In comparison, all other lines together took just 4 seconds to run.

The simple act of adding extra inputs to the callback has completely transformed the speed of our code.

How real is this example?
In code written for a customer, the context menu had a variety of sub menus (dependent on the parameter being plotted – from 5 to ~20), and they each had multiple parameters passed into the callbacks. Over a relatively short period of time the user would cycle through a lot of data and the number of uicontextmenus being created was surprisingly large. For example, users would easily look at 100 individual sensors recorded at 10Hz for 2 minutes (100*10*60*2). If all sensors and points are plotted individually that would be 120,000 uicontextmenus!

So how do we resolve this?

The problem is addressed by simply deleting the context menu handles once they are no longer needed. This can be done by adding a delete command after cla at the top of the redraw function, in order to remove the redundant uicontextmenus:

function redraw ( obj, event, h, varargin )
   cla(h.ax);
   delete ( findobj ( h.main, 'type','uicontextmenu' ) )   set ( h.msg, 'string','redrawing' );
   start = tic;
   ...

If we now click <redraw x20> we see that the number of objects and the time to create them remain its essentially the same as the first call: 1418 objects and 0.28 seconds:

figure contains 1418 objects - drawn in 0.28 seconds
figure contains 1418 objects - drawn in 0.30 seconds
figure contains 1418 objects - drawn in 0.33 seconds
figure contains 1418 objects - drawn in 0.29 seconds
figure contains 1418 objects - drawn in 0.29 seconds
...

Advanced users could use handle listeners to attached a callback such that when a plot element is deleted, so too are its associated context menus.

Conclusions

Things to consider with uicontextmenus:

  1. Always delete uicontextmenus that you have finished with.
  2. If you have multiple uicontextmenus you will want to only delete the ones associated with the axes being cleared – otherwise you will delete more than you want to.
  3. Try to reduce the number of input arguments to your callbacks, group into a structure or cell array.
  4. Re-use uicontextmenus where possible.

Have you had similar experiences? Or other issues where GUI slow down over time? If so, please leave a comment below.

 
Related posts:
  1. Performance: accessing handle properties Handle object property access (get/set) performance can be significantly improved using dot-notation. ...
  2. Plot performance Undocumented inner plot mechanisms can be used to significantly improved plotting performance...
  3. Performance: scatter vs. line In many circumstances, the line function can generate visually-identical plots as the scatter function, much faster...
  4. Preallocation performance Preallocation is a standard Matlab speedup technique. Still, it has several undocumented aspects. ...
 

Viewing all articles
Browse latest Browse all 219

Trending Articles