I recently consulted to a client who wanted to display an interactive plot with numerous data points that kept updating in real-time. Matlab’s standard plotting functions simply could not keep up with the rate of data change. Today, I want to share a couple of very simple undocumented hacks that significantly improve plotting performance and fixed my problem.
I begin by stating the obvious: whenever possible, try to vectorize your code, preallocate the data and other performance-improving techniques suggested by Matlab. Unfortunately, sometimes (as in my specific case above) all these cannot help. Even in such cases, we can still find important performance tricks, such as these:
Performance hack #1: manual limits
Whenever Matlab updates plot data, it checks whether any modification needs to be done to any of its limits. This computation-intensive task is done for any limit that is set to ‘Auto’ mode, which is the default axes limits mode. If instead we manually set the axes limits to the requested range, Matlab skips these checks, enabling much faster plotting performance.
Let us simulate the situation by adding 500 data points to a plot, one at a time:
>> x=0:0.02:10; y=sin(x); >> clf; cla; tic; >> drawnow; plot(x(1),y(1)); hold on; legend data; >> for idx = 1 : length(x); plot(x(idx),y(idx)); drawnow; end; >> toc Elapsed time is 21.583668 seconds. |
And now let’s use static axes limits:
>> x=0:0.02:10; y=sin(x); >> clf; cla; tic; drawnow; >> plot(x(1),y(1)); hold on; legend data; >> xlim([0,10]); ylim([-1,1]); % static limits >> for idx = 1 : length(x); plot(x(idx),y(idx)); drawnow; end; >> toc Elapsed time is 16.863090 seconds. |
Note that this trick is the basis for the performance improvement that occurs when using the plot’s undocumented set of LimInclude properties.
Of course, setting manual limits prevents the axes limits from growing and shrinking automatically with the data, which can actually be a very useful feature sometimes. But if performance is important, we now know that we have this tool to improve it.
Performance hack #2: static legend
Hack #1 gave us a 22% performance boost, but we can do much better. Running the profiler on the code above we see that much of the time is spent recomputing the legend. Looking inside the legend code (specifically, the legendcolorbarlayout function), we detect several short-circuits that we can use to make the legend static and prevent recomputation:
>> x=0:0.02:10; y=sin(x); >> clf; cla; tic; drawnow; >> plot(x(1),y(1)); hold on; legend data; >> xlim([0,10]); ylim([-1,1]); % static limits >> % Static legend >> set(gca,'LegendColorbarListeners',[]); >> setappdata(gca,'LegendColorbarManualSpace',1); >> setappdata(gca,'LegendColorbarReclaimSpace',1); >> for idx = 1 : length(x); plot(x(idx),y(idx)); drawnow; end; >> toc Elapsed time is 5.209053 seconds. |
Now this is much much better – a 76% performance boost compared to the original plot (i.e., 4 times faster!). Of course, it prevents the legend from being dynamically updated. Sometimes we actually wish for this dynamic effect (last year I explained how to use the legend’s undocumented -DynamicLegend feature for even greater dynamic control). But when performance is important, we can still display a legend without its usual performance cost.
In conclusion, I have demonstrated that Matlab performance can often be improved significantly, even in the absence of any vectorization, by simply understanding the internal mechanisms and bypassing those which are irrelevant in our specific case.
Have you found other similar performance hacks? If so, please share them in the comments section below.
I agree with Perttu. Whenever I want performance, I manually set the xdata and ydata. This doesn’t cause any of the automagic stuff (like limit picking and legend setting) to happen.
My spectrum scope on the file exchange shows this coding pattern, including tracking limits to make sure data isn’t cut off.
For comparison, your last code gives for me: 4.42 seconds, by replacing plot with line in for loop times to 4.0 seconds. In general line is more “raw” and thus faster than plot. However, nothing beats direct modification of the first plots x/ydata:
But obviously the result is not the same, since there is now only one line object instead of 501 that your code creates.
I have had similar experiences to the previous commenter. For updating an existing plot, it’s always much faster to modify the properties of an existing object than delete old ones and create new ones (as is done in repeated calls to plot). I would also add that in a large GUI with many graphics objects, calls to “gca”, “gco” and “gcf” start to slow down considerably, so it’s always better to store the handles in advance and avoid calling any of those functions to get the handles, particularly for operations that are being done repeatedly inside loops.
Yair,
First off – thanks.
Second – did you ever happen to need to “plot” icons instead of the usual circle/triangle markers?
I need to show icons (e.g. picture of a house, or a car) on the axes that behave like markers: not change size on zoom, and stay at the same axes coordinates.
Could you suggest how to do that?
@Yuri – I have never tried to use icons as markers. You can control the images by designing a simple zoom callback hook function, that gets invoked whenever the axes are zoomed:
Within your cbZoom callback function, simply reset all your marker images to the requested size.
Yair,
Don’t feel like you have to divulge all your secretes but I have to ask how do you find all these all these undocumented listeners and events? For instance I was recently trying to create a listener for when someone plotted on an axes and it was only after googling pretty extensively that I found the event that I needed to look for but could not find any information on how that person knew that the ‘ObjectChildAdded’ event existed. The same goes for the ‘LegendColorbarListeners’ property you use above. Is there anyway to find this information myself so I don’t have to bother you with unending questions? I tried using findjobj and uiinspect but didn’t see how to get the information from there.
Thanks,
Nate
@Nate – there are many sources to my knowledge: I learn from posts by other people on the CSSM newsgroup; I learn from publicly-available Matlab m-code (much of the Matlab code-based resides in regular editable m-files, including all the legend code); I learn from tools like uiinspect, findjobj and checkClass; I learn from other people who are gracious enough to report stuff to me; and I learn a lot by simple trial and error. There is never one single way.
I am not doing anything special that any other Matlab developer can’t do. But over the past years I have gathered lots of information and this helps me to more easily extrapolate where to look for answers, faster perhaps than most people. Still, I sometimes look for an answer to a particular question for many months, and I still do not know the answer to many things. There is still much left to be learned.
p.s. – here’s another humorous answer to the same question, by U(r)s Schwartz, who is one of my information sources. This is actually one of my favorite posts on CSSM 🙂
-Yair
hahaha. That is quite the post. Well thanks for putting a lot of the information in one easy to use website. While there might not be any other easy shortcuts your knowledge definitely helps out.
Thanks,
Nate
P.S. Thanks for the hint about the legend code. It is a treasure trove.
(1) Don’t forget to set(hAxes,’DrawMode’,’fast’) whenever appropriate! This will tell the renderer NOT to check
which of the lines, patches etc need to be displayed on top of the others. Instead, Matlab will simply redraw
objects following the order in which they were created. I do not know whether DrawMode=’fast’ will have any effect
if we set(hg,’XData’,…) directly. However, as far as I understand, DrawMode=’fast’ will never slow down plotting.
(2) If I know that out-of-range data can never occur, I am usually setting the Clipping property so that Matlab
won’t waste any time to check whether data beyond xlim/ylim may need to be excluded from display.
(3) Finally, lots of automatic checking and property reset actions can be avoided by set(hAxes,’NextPlot’,’replacechildren’);
(4) One note about unnecessary legend updates:
Whenever in doubt if legend updates might be an issue, I simply create a secondary axes, a2=copyobj(…),
draw some fake data points or lines outside xlim/ylim, create the legend once and set(a2,’Visible’,’off’);
Refreshing the data in the main (first) axes does then obviously not callback any of those nasty legend actions.
— btw: I did not run any performance tests for my suggestions, since actual performance improvement may vary
depending on the data/task. However, once again I found some great tips on this site. Thank to all of you!
H;
@Helge – thanks for the useful additional tips 🙂
[…] Different performance hotspots can have different solutions: caching, vectorization, internal library functions, undocumented graphics properties, smart property selection, smart function selection, smart indexing, smart parameter selection etc. […]
Not sure if this is obvious or not what you’re looking for, but I’m fairly certain the fastest way to plot is to plot to an invisible figure.
Here are my timings for your three:
And for Perttu’s:
Now with a minor modification to your original code:
would it not be more efficent to remove the drawnow from the loop and put it at the end? like so:
@ShabbyChef – indeed, but then you would not see the effect on the legend. My point in this article was simply to illustrate ways in which plot legends can be optimized. Naturally, plotting in an invisible figure and moving drawnow out of the loop would improve the plotting performance, as well as using the vectorized version of the plot function. But again, I wanted to illustrate the point about the legend, which is why I used a simplistic sub-optimal plotting loop.
Hello !
First of all thank you for all the tips you posted here!
I have the same problem with an interactive plot, a real time plot. I solved the problem but i wonder, is there a way to include this plot into a GUI? i can’t include the plot into a GUI figure, it always opens a new figure.
@Sabina – the following links may help you: link #1, link #2. If you still need my help, consider hiring my consulting services by sending me an email using the link at the top-right of this webpage.
[…] It is also faster to have calculate the X and Y scales statically instead of using auto […]
Hi Yair,
Thanks for the useful suggestion and other stuff elsewhere. You have helped me a lot!
Anyway, by combining your suggestion and Perttu’s along with ‘EraseMode’ = ‘none’ trick, the plotting speed could be boosted little further.
But now the legend is little messed up. I wonder how you would fix that without compromising the speed.
@Ippei – you could set up a single-shot timer that will run after some non-zero time delay, fixing the legend. This will enable your main function to process unhindered, and the legend will be updated a bit later, seemingly in parallel. In practice the timer would still use the main Matlab thread so the total run time would not improve, but the perceived performance would improve since the main bulk of you function would end sooner.
Alternately, you could set up the legend only after the main function ends, providing similar improved perceived performance although the total run-time will remain unchanged.
Thank you for the EraseMode suggestion. I am plotting in a GUI using the set data directly to YData (3 plots), while it worked OK I could only update the graph at about 12 Hz and still use 50% of a core. Now with EraseMode of the plots’ lines set to ‘background’, I can update the plots at a full 30 Hz and only use 4% of a core. HUGE difference.
thanks
[…] This can be very important for plot performance, since the legend would not need to be updated whenever these objects are modified […]
Won’t replacing xdata and ydata using ‘set’ make it faster than using plot command? When I replaced plot function with the following
I see 25% improvement. Did i miss something?
@Chandrakanth – indeed so, and this was already pointed-out by Perttu, Scott and others. However, my point in the article was to use static axes limits, and I just used the loop to show that it runs faster with static axes limits. We need to compare apples to apples, which is why I used exactly the same plot-update loop. It will also run faster with static limits if you change the loop to set the xdata/ydata properties rather than re-plot. So going back to your example: yes,
set(p,'xdata',...)
is faster, but it will be even faster if you use it with the static axes limits that I have shown (and even faster with a few other tricks).Thank you very much. I really enjoyed this topic!
i want to use matlab for REAL TIME plotting from the serial port …
When i run it in matlab , it runs very well , but it stops after about 47 sec , and every time i re-execute it it stops again in something like 47 sec …
i have improved the performance by doing what you said but without any improvement
what will be the problem
@chfakht – I assume that there is some programmatic or algorithmic bug in your program. From what you describe, it seems that the problem is not in the code performance (speed), but rather in the code correctness. If you’d like me to help you debug your program, contact me by email for a consulting offer.
[…] […]
Great post. How can I implement static legend in 2014b? Specifically, given the axes handle, it doesn’t let me implement your trick:
However, if I change it to setappdata , that works: setappdata(my_ax, ‘LegendColorbarListeners’,[]). I can also do the other two calls with setappdata, but they don’t seem to help performance at all. I set them before and after making the legend and also at beginning of my ‘update’ function, to no avail — still a whole bunch of slow legend updates. Any ideas to make static legend in 2014b?
@Alex – you can try the following in R2014b onward:
Note that there are less risky alternatives for disabling legend for specific plot lines, for example:
as described here: http://undocumentedmatlab.com/blog/handle-graphics-behavior
Thanks Yair, appreciate the response. I had a bug in setting static axis limits for the plot. I was not setting them on the plot with the legend (but rather another plot mistakenly). I set static limits by manually calculating my own max and min for x- and y-values. Once this was corrected, this got rid of all the legend update calls. It reduced the number of function calls as enumerated by profile(‘info’) from about 90 to 5, and time to update the plot (using set on XData and YData in all cases) from ~0.20 seconds to ~0.02 seconds, a 10x improvement. Persistence! Thanks, Yair.
dear all;
I’m working in my project and I want to draw 2 line in one axis, so I want to use set() function but I don’t know how to code, Anyone had this problem please let me know.
Thanks in advance
I’m trying to set the static axis and legends in app designer where I have a startup callback function in which I’m trying to read the COM port and when the terminator is reached a bytes available callback function has a imagesc to display the data. The x and y axis both are static and also the colormap and colorbar for imagesc, so I want to make them static but I don’t how exactly to do this in app designer. Because most of the examples are provided for GUIDE. Thanks for your time and effort! I would like to know if there are any similar documents for app designer or any link in which such slow interface problem in app designer is addressed.
@Kavya – I’m afraid that the slowness of uifigures (created by App-Designer) is due to inherent limitations in the uifigures, not to the dynamic/static nature of the colorbar or legend. It’s true that static colorbars/legend will improve the speed a bit, but this will most probably be overshaddowed by uifigure’s underlying slowness.
MathWorks is well aware of the slowness of uifigures and are actively improving this aspect in each new Matlab release, so upgrading to the latest Matlab release might help. However, note that uifigures are slower than Java-based figures even today (R2018b). Hopefully this performance gap will finally be closed in an upcoming Matlab release.
Hi,
I am trying to set the legend to Static, but this command seems not to work in R2022a anymore:
Any ideas?
THANKS / marcel
Marcel – 12 years and ~25 releases since I wrote this article, it is no surprise at all that some things no longer work the same way. In fact, the surprising thing is that many things still do work the same way, not the reverse. In this specific case, you’d need to rerun the profiler to see where the bottlenecks are in the new code, similar to what I described in the article.
Also see my response above to this matter.