{"id":249,"date":"2015-06-09T15:35:25","date_gmt":"2015-06-09T19:35:25","guid":{"rendered":"https:\/\/blogs.mathworks.com\/graphics\/?p=249"},"modified":"2015-06-09T15:35:25","modified_gmt":"2015-06-09T19:35:25","slug":"object-creation-performance","status":"publish","type":"post","link":"https:\/\/blogs.mathworks.com\/graphics\/2015\/06\/09\/object-creation-performance\/","title":{"rendered":"Object Creation Performance"},"content":{"rendered":"<div class=\"content\"><h3>Object Creation Performance<\/h3><p>Back in January, we looked at <a href=\"https:\/\/blogs.mathworks.com\/graphics\/2015\/01\/20\/performance-scaling\/\">the performance of creating a graphics object with a lot of data<\/a>. Today we're going to look at what is basically the opposite problem. What does the performance look like when we're creating a lot of graphics objects which each have a tiny amount of data?<\/p><p>In general, creating a large number of graphics objects which each have a small amount of data is going to be slower than creating a small number of graphics objects with a large amount of data.<\/p><p>We can see this pretty easily with the following example. I want to draw a thousand markers along this curve:<\/p><p><img decoding=\"async\" src=\"https:\/\/blogs.mathworks.com\/images\/graphics\/2015\/object_creation_performance_eq02080758610356490505.png\" alt=\"$$exp(i t) - exp(t i t)\/2 + i exp(-14 i t)\/3$$\"><\/p><pre class=\"codeinput\">t = linspace(0,2*pi,1000);\r\nv = exp(i*t) - exp(6*i*t)\/2 + i*exp(-14*i*t)\/3;\r\nx = real(v);\r\ny = imag(v);\r\n<\/pre><p>I could do this with 1,000 calls to scatter:<\/p><pre class=\"codeinput\">axes\r\nhold <span class=\"string\">on<\/span>\r\ns = 10;\r\ndrawnow\r\ntic\r\n<span class=\"keyword\">for<\/span> ix=1:1000\r\n   scatter(x(ix),y(ix),s,t(ix),<span class=\"string\">'filled'<\/span>);\r\n<span class=\"keyword\">end<\/span>\r\ndrawnow\r\ntoc\r\n<\/pre><pre class=\"codeoutput\">Elapsed time is 7.610161 seconds.\r\n<\/pre><img decoding=\"async\" vspace=\"5\" hspace=\"5\" src=\"https:\/\/blogs.mathworks.com\/images\/graphics\/2015\/object_creation_performance_01.png\" alt=\"\"> <p>Or I could do it with a single call to scatter:<\/p><pre class=\"codeinput\">hold <span class=\"string\">off<\/span>\r\ncla\r\ndrawnow\r\ntic\r\nscatter(x,y,s,t,<span class=\"string\">'filled'<\/span>)\r\ndrawnow\r\ntoc\r\n<\/pre><pre class=\"codeoutput\">Elapsed time is 0.056938 seconds.\r\n<\/pre><img decoding=\"async\" vspace=\"5\" hspace=\"5\" src=\"https:\/\/blogs.mathworks.com\/images\/graphics\/2015\/object_creation_performance_02.png\" alt=\"\"> <p>As you can see, creating a single scatter object with 1,000 points is about 150 times faster than creating 1,000 scatter objects which each have a single point.<\/p><p>But I want to generalize this and look at how it scales over different types and numbers of objects.<\/p><p>Lets start drilling into this by looking at the cost of creating 400 random lines. To get accurate data, I'll run this 15 times. That's because, as we'll see in a moment, there tends to be a bit of noise in these measurements. That's caused by other things running on the machine, where things are located in memory, and other things like that.<\/p><pre class=\"codeinput\">clf\r\ndrawnow\r\nnpass = 15;\r\nresults = zeros(1,npass);\r\nx1 = rand(1,400);\r\nx2 = rand(1,400);\r\ny1 = rand(1,400);\r\ny2 = rand(1,400);\r\n<span class=\"keyword\">for<\/span> ix=1:npass\r\n    cla\r\n    drawnow\r\n    tic\r\n    <span class=\"keyword\">for<\/span> j=1:400\r\n        line([x1(j) x2(j)],[y1(j) y2(j)])\r\n    <span class=\"keyword\">end<\/span>\r\n    drawnow\r\n    results(ix) = toc;\r\n<span class=\"keyword\">end<\/span>\r\n<\/pre><img decoding=\"async\" vspace=\"5\" hspace=\"5\" src=\"https:\/\/blogs.mathworks.com\/images\/graphics\/2015\/object_creation_performance_03.png\" alt=\"\"> <p>I prefer to look at this type of data as 'Number of objects created per second', so we'll look at the number of objects divided by the time.<\/p><p>And we'll also want to look at the median of those 15 runs. The median is the best choice here because it's relatively insensitive to anomalies where one run \"hiccups\" because something else on the machine got resources.<\/p><pre class=\"codeinput\">grey = [.75 .75 .75];\r\nplot(1:npass,400.\/results)\r\nxlabel(<span class=\"string\">'Run #'<\/span>)\r\nylabel(<span class=\"string\">'Line objects per second'<\/span>)\r\nmin_lines_per_second = min(400.\/results);\r\nmedian_lines_per_second = median(400.\/results)\r\nmax_lines_per_second = max(400.\/results);\r\nline([1 15],repmat(min_lines_per_second,[1 2]),<span class=\"string\">'Color'<\/span>,grey)\r\nline([1 15],repmat(median_lines_per_second,[1 2]),<span class=\"string\">'Color'<\/span>,<span class=\"string\">'green'<\/span>,<span class=\"string\">'LineWidth'<\/span>,2)\r\nline([1 15],repmat(max_lines_per_second,[1 2]),<span class=\"string\">'Color'<\/span>,grey)\r\nylim([0 inf])\r\n<\/pre><pre class=\"codeoutput\">\r\nmedian_lines_per_second =\r\n\r\n   1.8366e+03\r\n\r\n<\/pre><img decoding=\"async\" vspace=\"5\" hspace=\"5\" src=\"https:\/\/blogs.mathworks.com\/images\/graphics\/2015\/object_creation_performance_04.png\" alt=\"\"> <p>So the median is about 1,800 line objects per second.<\/p><p>But as I said when we were looking at the large data case, it's always good to take a look at how performance scales with different size problems. I ran this same code, varying the number of objects from 0 to 500. I did 4 runs at each size and then saved the median values to a MAT file. You can see that there's still a bit of noise, but this is good enough to work with.<\/p><pre class=\"codeinput\">load <span class=\"string\">R2015a_creation_results<\/span>\r\nplot(R2015a_creation_results.counts,R2015a_creation_results.counts.\/R2015a_creation_results.line_times);\r\nhold <span class=\"string\">on<\/span>\r\nerrorbar(400,median_lines_per_second,median_lines_per_second-min_lines_per_second,max_lines_per_second-median_lines_per_second,<span class=\"string\">'Color'<\/span>,<span class=\"string\">'red'<\/span>)\r\nxlabel(<span class=\"string\">'# of objects'<\/span>)\r\nylabel(<span class=\"string\">'Objects per second'<\/span>)\r\n<\/pre><img decoding=\"async\" vspace=\"5\" hspace=\"5\" src=\"https:\/\/blogs.mathworks.com\/images\/graphics\/2015\/object_creation_performance_05.png\" alt=\"\"> <p>That looks pretty good. We can see that there's some startup cost, as we saw when scaling the amount of data, but we're pretty close to full speed once we get to about 150 objects.<\/p><p>Now that we've measured the performance of creating a graphics object, what can we do to speed it up when it's the bottleneck? The best thing we can do is obviously to create fewer objects, but how?<\/p><p>If you remember <a href=\"https:\/\/blogs.mathworks.com\/graphics\/2015\/01\/20\/performance-scaling\/\">the post where I talked about performance scaling<\/a>, then you may remember that the left side of the performance curves was flat. This means that the time it takes to create a line object with 2 points is pretty much the same as the time it takes to create a line object with 10,000 points. We can actually take advantange of that here by using a very old MATLAB graphics programming trick.<\/p><p>The basic idea is that you can put nan values into the X &amp; Y data to break a line into multiple segments. To do this, we want to create arrays that look like this:<\/p><pre class=\"language-matlab\">x3 = [x1(1), x2(1), nan, x1(2), x2(2), nan, x1(3), x2(3), nan, <span class=\"keyword\">...<\/span>\r\ny3 = [y1(1), y2(1), nan, y1(2), y2(2), nan, y1(3), y2(3), nan, <span class=\"keyword\">...<\/span>\r\n<\/pre><p>That's actually pretty easily done:<\/p><pre class=\"codeinput\"><span class=\"keyword\">for<\/span> ix=1:npass\r\n    cla\r\n    drawnow\r\n    tic\r\n    x3 = [x1; x2; nan(1,400)];\r\n    y3 = [y1; y2; nan(1,400)];\r\n    x3 = x3(:);\r\n    y3 = y3(:);\r\n    line(x3,y3)\r\n    drawnow\r\n    results(ix) = toc;\r\n<span class=\"keyword\">end<\/span>\r\n<\/pre><img decoding=\"async\" vspace=\"5\" hspace=\"5\" src=\"https:\/\/blogs.mathworks.com\/images\/graphics\/2015\/object_creation_performance_06.png\" alt=\"\"> <p>The performance of doing it this way is much, much better!<\/p><pre class=\"codeinput\">plot(1:npass,400.\/results)\r\nxlabel(<span class=\"string\">'Run #'<\/span>)\r\nylabel(<span class=\"string\">'Line segments per second'<\/span>)\r\nmin_lines_per_second = min(400.\/results);\r\nmedian_lines_per_second = median(400.\/results)\r\nmax_lines_per_second = max(400.\/results);\r\nline([1 15],repmat(min_lines_per_second,[1 2]),<span class=\"string\">'Color'<\/span>,grey)\r\nline([1 15],repmat(median_lines_per_second,[1 2]),<span class=\"string\">'Color'<\/span>,<span class=\"string\">'green'<\/span>,<span class=\"string\">'LineWidth'<\/span>,2)\r\nline([1 15],repmat(max_lines_per_second,[1 2]),<span class=\"string\">'Color'<\/span>,grey)\r\nylim([0 inf])\r\n<\/pre><pre class=\"codeoutput\">\r\nmedian_lines_per_second =\r\n\r\n   3.5182e+04\r\n\r\n<\/pre><img decoding=\"async\" vspace=\"5\" hspace=\"5\" src=\"https:\/\/blogs.mathworks.com\/images\/graphics\/2015\/object_creation_performance_07.png\" alt=\"\"> <p>Well that certainly helped. We've gone from 1,800 lines per second up to 35,000 lines per second. That's almost a 20X speedup!<\/p><p>The problem with this old trick is that it isn't a one size fits all solution. You need to do things a little differently for different types of graphics objects. So we'll look at a couple of different types of graphics objects. While we do that, let's also look at the object creation performance in different versions of MATLAB.<\/p><p>Here's some data I collected for creating 7 different types of objects in each of the last 3 versions of MATLAB.<\/p><pre class=\"codeinput\">clf\r\nload <span class=\"string\">R2014a_create400_results<\/span>\r\nload <span class=\"string\">R2014b_create400_results<\/span>\r\nload <span class=\"string\">R2015a_create400_results<\/span>\r\nload <span class=\"string\">R2015b_Prerelease_create400_results<\/span>\r\nplot_create400(R2014a_create400_results, <span class=\"keyword\">...<\/span>\r\n               R2014b_create400_results, <span class=\"keyword\">...<\/span>\r\n               R2015a_create400_results)\r\nlegend(<span class=\"string\">'R2014a'<\/span>,<span class=\"string\">'R2014b'<\/span>,<span class=\"string\">'R2015a'<\/span>)\r\ntitle(<span class=\"string\">'Graphics Object Creation Performance'<\/span>)\r\n<\/pre><img decoding=\"async\" vspace=\"5\" hspace=\"5\" src=\"https:\/\/blogs.mathworks.com\/images\/graphics\/2015\/object_creation_performance_08.png\" alt=\"\"> <p>There's a lot going on in this chart. The first thing that we notice is that object creation took a big performance hit when the new graphics system was introduced in R2014b. That's because when the graphics objects got converted to real MATLAB classes, they lost a bunch of the performance tweaks that had been added to the old graphics system over the decades.<\/p><p>We can also see that the amount of slowdown depends on the type of object. It varies from taking taking about 40% longer to create an image, to taking about 7 times as long to create an axes. Both of those extremes are actually special cases. For most of the graphics objects (such as the line), the slowdown was about 4 times as long. We're slowly adding performance tweaks back into the new objects, as you can see for the line and text object in R2015a, but it will be quite a while before it catches up with the old system. This means that in any case where you need performance, you're going to want to optimize your code so you're not creating too many graphics objects. This was a good idea in earlier versions of MATLAB, but it is even more important in newer versions.<\/p><p>OK, so lets go back to our combining objects trick. The first thing to note is that it was already a good idea in older versions of MATLAB. That's why that trick of putting nans in to combine lines is an \"old trick\".<\/p><p>But how can we use the combining trick with these other objects?<\/p><p>We've already seen how to combine calls to scatter and line. Patch is just a little bit trickier. If we're using the Face\/Vertex form of patch, we can just concatenate the Vertices array, but we need to add an offset to one of the Faces arrays. So this:<\/p><pre class=\"codeinput\">clf\r\ndrawnow\r\ntic\r\n<span class=\"keyword\">for<\/span> ix=0:99\r\n    a1 =  ix   *2*pi\/100;\r\n    a2 = (ix+1)*2*pi\/100;\r\n    v = [cos(a1), sin(a1), -1; <span class=\"keyword\">...<\/span>\r\n         cos(a2), sin(a2), -1; <span class=\"keyword\">...<\/span>\r\n         cos(a2), sin(a2),  1; <span class=\"keyword\">...<\/span>\r\n         cos(a1), sin(a1),  1];\r\n    f = 1:4;\r\n    patch(<span class=\"string\">'Vertices'<\/span>,v,<span class=\"string\">'Faces'<\/span>,f,<span class=\"string\">'FaceColor'<\/span>,<span class=\"string\">'yellow'<\/span>);\r\n<span class=\"keyword\">end<\/span>\r\nview(3)\r\ndrawnow\r\ntoc\r\n<\/pre><pre class=\"codeoutput\">Elapsed time is 0.111010 seconds.\r\n<\/pre><img decoding=\"async\" vspace=\"5\" hspace=\"5\" src=\"https:\/\/blogs.mathworks.com\/images\/graphics\/2015\/object_creation_performance_09.png\" alt=\"\"> <p>Becomes this:<\/p><pre class=\"codeinput\">clf\r\ndrawnow\r\ntic\r\nverts = [];\r\nfaces = [];\r\n<span class=\"keyword\">for<\/span> ix=0:99\r\n    a1 =  ix   *2*pi\/100;\r\n    a2 = (ix+1)*2*pi\/100;\r\n    v = [cos(a1), sin(a1), -1; <span class=\"keyword\">...<\/span>\r\n         cos(a2), sin(a2), -1; <span class=\"keyword\">...<\/span>\r\n         cos(a2), sin(a2),  1; <span class=\"keyword\">...<\/span>\r\n         cos(a1), sin(a1),  1];\r\n    f = 1:4;\r\n    verts = [verts; v];\r\n    faces = [faces; f + 4*ix];\r\n<span class=\"keyword\">end<\/span>\r\npatch(<span class=\"string\">'Vertices'<\/span>,verts,<span class=\"string\">'Faces'<\/span>,faces,<span class=\"string\">'FaceColor'<\/span>,<span class=\"string\">'yellow'<\/span>)\r\nview(3)\r\ndrawnow\r\ntoc\r\n<\/pre><pre class=\"codeoutput\">Elapsed time is 0.026823 seconds.\r\n<\/pre><img decoding=\"async\" vspace=\"5\" hspace=\"5\" src=\"https:\/\/blogs.mathworks.com\/images\/graphics\/2015\/object_creation_performance_10.png\" alt=\"\"> <p>Some of the others types of objects can be harder to combine though. The most important one is the axes. As we saw above, creating axes slowed down quite a bit in R2014b. That's largly because we've added a lot of features to the new version of axes. You've seen a couple of those features in the last two releases, but you'll be seeing a lot more of them when R2015b comes out this summer.<\/p><p>There are basically two different ways in which axes objects are used. The first is to actually use them as an axes to hold a chart. In this case, you want the ticks and labels and things. The other case is just as a container to draw something in. In this second case, you usually turn the Visible property of the axes off.<\/p><p>There isn't a lot you can do to combine axes in the first case. The subplot command will create a grid of axes, but it does that by actually creating a bunch of axes objects, so there isn't a shortcut there. The good news is that you usually don't create an awful lot of axes objects when you use them this way. Creating 400 axes objects in a single figure usually results in them being too small to read.<\/p><p>But in the second case, where we're turning the Visible property off, it often is pretty easy to combine several axes into a single object. Let's look at an example.<\/p><p>Here is an example from the MATLAB newsgroup.<\/p><pre class=\"codeinput\">fw = 640;\r\nfh = 640;\r\nfig = figure(<span class=\"string\">'Units'<\/span>,<span class=\"string\">'pixels'<\/span>,<span class=\"string\">'Position'<\/span>,[0 0 fw fh]);\r\nnRows = 40;\r\nnKols = 10;\r\nheight = (fh-2)\/nRows;\r\nwidth = (fw-2)\/nKols;\r\ntic\r\n<span class=\"keyword\">for<\/span> iRow = 1:nRows\r\n    <span class=\"keyword\">for<\/span> iKol = 1:nKols\r\n        axes(<span class=\"string\">'Units'<\/span>,<span class=\"string\">'pixels'<\/span>,<span class=\"string\">'Position'<\/span>,[1+(iKol-1)*width, fh-iRow*height, width, height], <span class=\"keyword\">...<\/span>\r\n             <span class=\"string\">'Visible'<\/span>,<span class=\"string\">'off'<\/span>);\r\n        text(.5,.5,[<span class=\"string\">'Ax '<\/span>, num2str(iRow), <span class=\"string\">','<\/span>, num2str(iKol)], <span class=\"keyword\">...<\/span>\r\n             <span class=\"string\">'HorizontalAlignment'<\/span>,<span class=\"string\">'center'<\/span>)\r\n    <span class=\"keyword\">end<\/span>\r\n<span class=\"keyword\">end<\/span>\r\ndrawnow\r\ntoc\r\n<\/pre><pre class=\"codeoutput\">Elapsed time is 1.807279 seconds.\r\n<\/pre><img decoding=\"async\" vspace=\"5\" hspace=\"5\" src=\"https:\/\/blogs.mathworks.com\/images\/graphics\/2015\/object_creation_performance_11.png\" alt=\"\"> <p>It's actually pretty easy to turn that into a single axes with a bunch of text objects in it:<\/p><pre class=\"codeinput\">delete(fig)\r\nfig = figure(<span class=\"string\">'Units'<\/span>,<span class=\"string\">'pixels'<\/span>,<span class=\"string\">'Position'<\/span>,[0 0 fw fh]);\r\ntic\r\naxes(<span class=\"string\">'Position'<\/span>,[0 0 1 1],<span class=\"string\">'Visible'<\/span>,<span class=\"string\">'off'<\/span>)\r\nxlim([1 fw])\r\nylim([1 fh])\r\n<span class=\"keyword\">for<\/span> iRow = 1:nRows\r\n    <span class=\"keyword\">for<\/span> iKol = 1:nKols\r\n        text(1+(iKol-.5)*width, fh-(iRow-.5)*height, <span class=\"keyword\">...<\/span>\r\n             [<span class=\"string\">'Ax '<\/span>, num2str(iRow), <span class=\"string\">','<\/span>, num2str(iKol)], <span class=\"keyword\">...<\/span>\r\n             <span class=\"string\">'HorizontalAlignment'<\/span>,<span class=\"string\">'center'<\/span>)\r\n    <span class=\"keyword\">end<\/span>\r\n<span class=\"keyword\">end<\/span>\r\ndrawnow\r\ntoc\r\n<\/pre><pre class=\"codeoutput\">Elapsed time is 0.567477 seconds.\r\n<\/pre><img decoding=\"async\" vspace=\"5\" hspace=\"5\" src=\"https:\/\/blogs.mathworks.com\/images\/graphics\/2015\/object_creation_performance_12.png\" alt=\"\"> <p>That helps quite a bit, but we've still got all of those text objects. Unfortunately those are a lot harder to combine. We can combine all of the strings in one column by using a cell array:<\/p><pre class=\"codeinput\">delete(fig)\r\n<span class=\"comment\">%clf<\/span>\r\nfig = figure(<span class=\"string\">'Units'<\/span>,<span class=\"string\">'pixels'<\/span>,<span class=\"string\">'Position'<\/span>,[0 0 fw fh]);\r\ntic\r\naxes(<span class=\"string\">'Position'<\/span>,[0 0 1 1],<span class=\"string\">'Visible'<\/span>,<span class=\"string\">'off'<\/span>)\r\nxlim([1 fw])\r\nylim([1 fh])\r\n<span class=\"keyword\">for<\/span> iKol = 1:nKols\r\n    strings = {};\r\n    <span class=\"keyword\">for<\/span> iRow = 1:nRows\r\n        strings{end+1} = [<span class=\"string\">'Ax '<\/span>, num2str(iRow), <span class=\"string\">','<\/span>, num2str(iKol)];\r\n    <span class=\"keyword\">end<\/span>\r\n    text(1+(iKol-.5)*width, fw\/2, strings, <span class=\"keyword\">...<\/span>\r\n             <span class=\"string\">'HorizontalAlignment'<\/span>,<span class=\"string\">'center'<\/span>, <span class=\"keyword\">...<\/span>\r\n             <span class=\"string\">'VerticalAlignment'<\/span>,<span class=\"string\">'middle'<\/span>);\r\n<span class=\"keyword\">end<\/span>\r\ndrawnow\r\ntoc\r\n<\/pre><pre class=\"codeoutput\">Elapsed time is 0.175428 seconds.\r\n<\/pre><img decoding=\"async\" vspace=\"5\" hspace=\"5\" src=\"https:\/\/blogs.mathworks.com\/images\/graphics\/2015\/object_creation_performance_13.png\" alt=\"\"> <p>But as you can see, this isn't a perfect fix because it can be really hard to accurately control the vertical spacing. That means that you're only going to be able to use this approach for text in some special cases. You may find that the performance gain isn't enough to make up for the lose of placement control.<\/p><p>Both image and surface can also be tricky to combine into a single object. So the approach of combining several graphics objects into a single one isn't a perfect solution to the problem of object creation performance, but it is an important technique to know about when you're trying to make your MATLAB Graphics code run as fast as possible.<\/p><script language=\"JavaScript\"> <!-- \r\n    function grabCode_47eace941e8342d0a67690ef5f03c4fc() {\r\n        \/\/ Remember the title so we can use it in the new page\r\n        title = document.title;\r\n\r\n        \/\/ Break up these strings so that their presence\r\n        \/\/ in the Javascript doesn't mess up the search for\r\n        \/\/ the MATLAB code.\r\n        t1='47eace941e8342d0a67690ef5f03c4fc ' + '##### ' + 'SOURCE BEGIN' + ' #####';\r\n        t2='##### ' + 'SOURCE END' + ' #####' + ' 47eace941e8342d0a67690ef5f03c4fc';\r\n    \r\n        b=document.getElementsByTagName('body')[0];\r\n        i1=b.innerHTML.indexOf(t1)+t1.length;\r\n        i2=b.innerHTML.indexOf(t2);\r\n \r\n        code_string = b.innerHTML.substring(i1, i2);\r\n        code_string = code_string.replace(\/REPLACE_WITH_DASH_DASH\/g,'--');\r\n\r\n        \/\/ Use \/x3C\/g instead of the less-than character to avoid errors \r\n        \/\/ in the XML parser.\r\n        \/\/ Use '\\x26#60;' instead of '<' so that the XML parser\r\n        \/\/ doesn't go ahead and substitute the less-than character. \r\n        code_string = code_string.replace(\/\\x3C\/g, '\\x26#60;');\r\n\r\n        copyright = 'Copyright 2015 The MathWorks, Inc.';\r\n\r\n        w = window.open();\r\n        d = w.document;\r\n        d.write('<pre>\\n');\r\n        d.write(code_string);\r\n\r\n        \/\/ Add copyright line at the bottom if specified.\r\n        if (copyright.length > 0) {\r\n            d.writeln('');\r\n            d.writeln('%%');\r\n            if (copyright.length > 0) {\r\n                d.writeln('% _' + copyright + '_');\r\n            }\r\n        }\r\n\r\n        d.write('<\/pre>\\n');\r\n\r\n        d.title = title + ' (MATLAB code)';\r\n        d.close();\r\n    }   \r\n     --> <\/script><p style=\"text-align: right; font-size: xx-small; font-weight:lighter;   font-style: italic; color: gray\"><br><a href=\"javascript:grabCode_47eace941e8342d0a67690ef5f03c4fc()\"><span style=\"font-size: x-small;        font-style: italic;\">Get \r\n      the MATLAB code <noscript>(requires JavaScript)<\/noscript><\/span><\/a><br><br>\r\n      Published with MATLAB&reg; R2015a<br><\/p><\/div><!--\r\n47eace941e8342d0a67690ef5f03c4fc ##### SOURCE BEGIN #####\r\n%% Object Creation Performance\r\n% Back in January, we looked at <https:\/\/blogs.mathworks.com\/graphics\/2015\/01\/20\/performance-scaling\/\r\n% the performance of creating a graphics object with a lot of data>. Today\r\n% we're going to look at what is basically the opposite problem. What does\r\n% the performance look like when we're creating a lot of graphics objects\r\n% which each have a tiny amount of data?\r\n%\r\n% In general, creating a large number of graphics objects which each have a\r\n% small amount of data is going to be slower than creating a small number\r\n% of graphics objects with a large amount of data.\r\n%\r\n% We can see this pretty easily with the following example. I want to draw\r\n% a thousand markers along this curve:\r\n%\r\n% $$exp(i t) - exp(t i t)\/2 + i exp(-14 i t)\/3\r\n%\r\nt = linspace(0,2*pi,1000);\r\nv = exp(i*t) - exp(6*i*t)\/2 + i*exp(-14*i*t)\/3;\r\nx = real(v);\r\ny = imag(v);\r\n\r\n%%\r\n% I could do this with 1,000 calls to scatter:\r\naxes\r\nhold on\r\ns = 10;\r\ndrawnow\r\ntic\r\nfor ix=1:1000\r\n   scatter(x(ix),y(ix),s,t(ix),'filled');\r\nend\r\ndrawnow\r\ntoc\r\n\r\n%%\r\n% Or I could do it with a single call to scatter:\r\nhold off\r\ncla \r\ndrawnow\r\ntic\r\nscatter(x,y,s,t,'filled')\r\ndrawnow\r\ntoc\r\n\r\n%%\r\n% As you can see, creating a single scatter object with 1,000 points is \r\n% about 150 times faster than creating 1,000 scatter objects which each \r\n% have a single point.\r\n%\r\n% But I want to generalize this and look at how it scales over different\r\n% types and numbers of objects. \r\n%\r\n% Lets start drilling into this by looking at the cost of creating 400 \r\n% random lines. To get accurate data, I'll run this 15 \r\n% times. That's because, as we'll see in a moment, there tends to be a bit\r\n% of noise in these measurements. That's caused\r\n% by other things running on the machine, where things are located in\r\n% memory, and other things like that.\r\nclf\r\ndrawnow\r\nnpass = 15;\r\nresults = zeros(1,npass);\r\nx1 = rand(1,400);\r\nx2 = rand(1,400);\r\ny1 = rand(1,400);\r\ny2 = rand(1,400);\r\nfor ix=1:npass\r\n    cla\r\n    drawnow\r\n    tic\r\n    for j=1:400\r\n        line([x1(j) x2(j)],[y1(j) y2(j)])\r\n    end\r\n    drawnow\r\n    results(ix) = toc;\r\nend\r\n\r\n%%\r\n% I prefer to look \r\n% at this type of data as 'Number of objects created per second', so we'll \r\n% look at the number of objects divided by the time. \r\n% \r\n% And we'll also want to look at the median of those 15 runs. The median is\r\n% the best choice here because it's relatively insensitive to anomalies\r\n% where one run \"hiccups\" because something else on the machine got\r\n% resources.\r\n%\r\ngrey = [.75 .75 .75];\r\nplot(1:npass,400.\/results)\r\nxlabel('Run #')\r\nylabel('Line objects per second')\r\nmin_lines_per_second = min(400.\/results);\r\nmedian_lines_per_second = median(400.\/results)\r\nmax_lines_per_second = max(400.\/results);\r\nline([1 15],repmat(min_lines_per_second,[1 2]),'Color',grey)\r\nline([1 15],repmat(median_lines_per_second,[1 2]),'Color','green','LineWidth',2)\r\nline([1 15],repmat(max_lines_per_second,[1 2]),'Color',grey)\r\nylim([0 inf])\r\n\r\n%%\r\n% So the median is about 1,800 line objects per second.\r\n%\r\n% But as I said when we were looking at the large data case, it's always\r\n% good to take a look at how performance scales with different size\r\n% problems. I ran this same code, varying the number of objects from 0 to\r\n% 500. I did 4 runs at each size and then saved the median values to a MAT file. \r\n% You can see that there's still a bit of noise, but this is good enough to work with.\r\nload R2015a_creation_results\r\nplot(R2015a_creation_results.counts,R2015a_creation_results.counts.\/R2015a_creation_results.line_times);\r\nhold on\r\nerrorbar(400,median_lines_per_second,median_lines_per_second-min_lines_per_second,max_lines_per_second-median_lines_per_second,'Color','red')\r\nxlabel('# of objects')\r\nylabel('Objects per second')\r\n\r\n%%\r\n% That looks pretty good. We can see that there's some startup cost, as we\r\n% saw when scaling the amount of data, but we're pretty close to full speed\r\n% once we get to about 150 objects.\r\n% \r\n% Now that we've measured the performance of creating a graphics object, what can\r\n% we do to speed it up when it's the bottleneck? The best thing we can do is\r\n% obviously to create fewer objects, but how?\r\n%\r\n% If you remember <https:\/\/blogs.mathworks.com\/graphics\/2015\/01\/20\/performance-scaling\/\r\n% the post where I talked about performance scaling>, then you may remember that\r\n% the left side of the performance curves was flat. This means that the time it\r\n% takes to create a line object with 2 points is pretty much the same as the time\r\n% it takes to create a line object with 10,000 points. We can actually take\r\n% advantange of that here by using a very old MATLAB graphics programming trick.\r\n%\r\n% The basic idea is that you can put nan values into the X & Y data to break a\r\n% line into multiple segments. To do this, we want to create arrays that\r\n% look like this:\r\n%\r\n%   x3 = [x1(1), x2(1), nan, x1(2), x2(2), nan, x1(3), x2(3), nan, ...\r\n%   y3 = [y1(1), y2(1), nan, y1(2), y2(2), nan, y1(3), y2(3), nan, ...\r\n%\r\n% That's actually pretty easily done:\r\nfor ix=1:npass\r\n    cla\r\n    drawnow\r\n    tic\r\n    x3 = [x1; x2; nan(1,400)];\r\n    y3 = [y1; y2; nan(1,400)];\r\n    x3 = x3(:);\r\n    y3 = y3(:);\r\n    line(x3,y3)\r\n    drawnow\r\n    results(ix) = toc;\r\nend\r\n\r\n%%\r\n% The performance of doing it this way is much, much better!\r\nplot(1:npass,400.\/results)\r\nxlabel('Run #')\r\nylabel('Line segments per second')\r\nmin_lines_per_second = min(400.\/results);\r\nmedian_lines_per_second = median(400.\/results)\r\nmax_lines_per_second = max(400.\/results);\r\nline([1 15],repmat(min_lines_per_second,[1 2]),'Color',grey)\r\nline([1 15],repmat(median_lines_per_second,[1 2]),'Color','green','LineWidth',2)\r\nline([1 15],repmat(max_lines_per_second,[1 2]),'Color',grey)\r\nylim([0 inf])\r\n\r\n%%\r\n% Well that certainly helped. We've gone from 1,800 lines per second up to\r\n% 35,000 lines per second. That's almost a 20X speedup!\r\n%\r\n% The problem with this old trick is that it isn't a one size fits all solution.\r\n% You need to do things a little differently for different types of graphics\r\n% objects. So we'll look at a couple of different types of graphics objects.\r\n% While we do that, let's also look at the object creation performance in\r\n% different versions of MATLAB.\r\n%\r\n% Here's some data I collected for creating 7 different types of objects in\r\n% each of the last 3 versions of MATLAB.\r\n%\r\nclf\r\nload R2014a_create400_results\r\nload R2014b_create400_results\r\nload R2015a_create400_results\r\nload R2015b_Prerelease_create400_results\r\nplot_create400(R2014a_create400_results, ...\r\n               R2014b_create400_results, ...\r\n               R2015a_create400_results)\r\nlegend('R2014a','R2014b','R2015a')\r\ntitle('Graphics Object Creation Performance')\r\n\r\n%%\r\n% There's a lot going on in this chart. The first thing that we notice is\r\n% that object creation took a big performance hit when the new graphics system\r\n% was introduced in R2014b. That's because when the graphics objects got\r\n% converted to real MATLAB classes, they lost a bunch of the performance\r\n% tweaks that had been added to the old graphics system over the decades.\r\n%\r\n% We can also see that the amount of slowdown depends on the type of\r\n% object. It varies from taking taking about 40% longer to create an image,\r\n% to taking about 7 times as long to create an axes. Both of those extremes\r\n% are actually special cases. For most of the graphics objects (such as the line), \r\n% the slowdown was about 4 times as long. We're slowly adding performance\r\n% tweaks back into the new objects, as you can see for the line and text\r\n% object in R2015a, but it will be quite a while before it catches up with\r\n% the old system. This means that in any case where you need performance,\r\n% you're going to want to optimize your code so you're not creating too\r\n% many graphics objects. This was a good idea in earlier versions of\r\n% MATLAB, but it is even more important in newer versions.\r\n%\r\n% OK, so lets go back to our combining objects trick. The first thing to\r\n% note is that it was already a good idea in older versions of MATLAB.\r\n% That's why that trick of putting nans in to combine lines is an \"old\r\n% trick\".\r\n%\r\n% But how can we use the combining trick with these other objects? \r\n\r\n%%\r\n% We've already seen how to combine calls to scatter and line. Patch is \r\n% just a little bit trickier. If we're using the Face\/Vertex form\r\n% of patch, we can just concatenate the Vertices array, but we need to add\r\n% an offset to one of the Faces arrays. So this:\r\n%\r\nclf\r\ndrawnow\r\ntic\r\nfor ix=0:99\r\n    a1 =  ix   *2*pi\/100;\r\n    a2 = (ix+1)*2*pi\/100;\r\n    v = [cos(a1), sin(a1), -1; ...\r\n         cos(a2), sin(a2), -1; ...\r\n         cos(a2), sin(a2),  1; ...\r\n         cos(a1), sin(a1),  1];\r\n    f = 1:4;\r\n    patch('Vertices',v,'Faces',f,'FaceColor','yellow');\r\nend\r\nview(3)\r\ndrawnow\r\ntoc\r\n%%\r\n% Becomes this:\r\nclf\r\ndrawnow \r\ntic\r\nverts = [];\r\nfaces = [];\r\nfor ix=0:99\r\n    a1 =  ix   *2*pi\/100;\r\n    a2 = (ix+1)*2*pi\/100;\r\n    v = [cos(a1), sin(a1), -1; ...\r\n         cos(a2), sin(a2), -1; ...\r\n         cos(a2), sin(a2),  1; ...\r\n         cos(a1), sin(a1),  1];\r\n    f = 1:4;\r\n    verts = [verts; v];\r\n    faces = [faces; f + 4*ix];\r\nend\r\npatch('Vertices',verts,'Faces',faces,'FaceColor','yellow')\r\nview(3)\r\ndrawnow\r\ntoc\r\n\r\n%%\r\n% Some of the others types of objects can be harder to combine though.\r\n% The most important one is the axes. As we saw above, creating axes slowed\r\n% down quite a bit in R2014b. That's largly because we've added a lot of\r\n% features to the new version of axes. You've seen a couple of those\r\n% features in the last two releases, but you'll be seeing a lot more of\r\n% them when R2015b comes out this summer. \r\n%\r\n% There are basically two different ways in which axes objects are used.\r\n% The first is to actually use them as an axes to hold a chart. In this\r\n% case, you want the ticks and labels and things. The other case is just\r\n% as a container to draw something in. In this second case, you usually\r\n% turn the Visible property of the axes off. \r\n%\r\n% There isn't a lot you can do to combine axes in the first case. The\r\n% subplot command will create a grid of axes, but it does that by actually\r\n% creating a bunch of axes objects, so there isn't a shortcut there. The\r\n% good news is that you usually don't create an awful lot of axes objects\r\n% when you use them this way. Creating 400 axes objects in a single figure\r\n% usually results in them being too small to read.\r\n%\r\n% But in the second case, where we're turning the Visible property off, it\r\n% often is pretty easy to combine several axes into a single object. Let's\r\n% look at an example. \r\n%\r\n% Here is an example from the MATLAB newsgroup.\r\n%\r\nfw = 640;\r\nfh = 640;\r\nfig = figure('Units','pixels','Position',[0 0 fw fh]);\r\nnRows = 40;\r\nnKols = 10;\r\nheight = (fh-2)\/nRows;\r\nwidth = (fw-2)\/nKols;\r\ntic\r\nfor iRow = 1:nRows\r\n    for iKol = 1:nKols\r\n        axes('Units','pixels','Position',[1+(iKol-1)*width, fh-iRow*height, width, height], ...\r\n             'Visible','off');\r\n        text(.5,.5,['Ax ', num2str(iRow), ',', num2str(iKol)], ...\r\n             'HorizontalAlignment','center')\r\n    end\r\nend\r\ndrawnow\r\ntoc\r\n\r\n%%\r\n% It's actually pretty easy to turn that into a single axes with a bunch of\r\n% text objects in it:\r\ndelete(fig)\r\nfig = figure('Units','pixels','Position',[0 0 fw fh]);\r\ntic\r\naxes('Position',[0 0 1 1],'Visible','off')\r\nxlim([1 fw])\r\nylim([1 fh])\r\nfor iRow = 1:nRows\r\n    for iKol = 1:nKols\r\n        text(1+(iKol-.5)*width, fh-(iRow-.5)*height, ...\r\n             ['Ax ', num2str(iRow), ',', num2str(iKol)], ...\r\n             'HorizontalAlignment','center')\r\n    end\r\nend\r\ndrawnow\r\ntoc\r\n\r\n%%\r\n% That helps quite a bit, but we've still got all of those text objects. \r\n% Unfortunately those are a lot harder to combine. We can combine all of\r\n% the strings in one column by using a cell array:\r\ndelete(fig)\r\n%clf\r\nfig = figure('Units','pixels','Position',[0 0 fw fh]);\r\ntic\r\naxes('Position',[0 0 1 1],'Visible','off')\r\nxlim([1 fw])\r\nylim([1 fh])\r\nfor iKol = 1:nKols\r\n    strings = {};\r\n    for iRow = 1:nRows\r\n        strings{end+1} = ['Ax ', num2str(iRow), ',', num2str(iKol)];\r\n    end\r\n    text(1+(iKol-.5)*width, fw\/2, strings, ...\r\n             'HorizontalAlignment','center', ...\r\n             'VerticalAlignment','middle');\r\nend\r\ndrawnow\r\ntoc\r\n\r\n%%\r\n% But as you can see, this isn't a perfect fix because it can be really hard \r\n% to accurately control the vertical spacing. That means that you're only going to be\r\n% able to use this approach for text in some special cases. You may find\r\n% that the performance gain isn't enough to make up for the lose of\r\n% placement control.\r\n%\r\n% Both image and surface can also be tricky to combine into a single\r\n% object. So the approach of combining several graphics objects into a\r\n% single one isn't a perfect solution to the problem of object creation\r\n% performance, but it is an important trick to know about when you're trying to make\r\n% your MATLAB Graphics code run as fast as possible.\r\n%\r\n\r\n##### SOURCE END ##### 47eace941e8342d0a67690ef5f03c4fc\r\n-->","protected":false},"excerpt":{"rendered":"<div class=\"overview-image\"><img src=\"https:\/\/blogs.mathworks.com\/graphics\/files\/feature_image\/object_creation_thumbnail.png\" class=\"img-responsive attachment-post-thumbnail size-post-thumbnail wp-post-image\" alt=\"\" decoding=\"async\" loading=\"lazy\" \/><\/div><p>Object Creation PerformanceBack in January, we looked at the performance of creating a graphics object with a lot of data. Today we're going to look at what is basically the opposite problem. What... <a class=\"read-more\" href=\"https:\/\/blogs.mathworks.com\/graphics\/2015\/06\/09\/object-creation-performance\/\">read more >><\/a><\/p>","protected":false},"author":89,"featured_media":254,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":[],"categories":[8],"tags":[],"_links":{"self":[{"href":"https:\/\/blogs.mathworks.com\/graphics\/wp-json\/wp\/v2\/posts\/249"}],"collection":[{"href":"https:\/\/blogs.mathworks.com\/graphics\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/blogs.mathworks.com\/graphics\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/blogs.mathworks.com\/graphics\/wp-json\/wp\/v2\/users\/89"}],"replies":[{"embeddable":true,"href":"https:\/\/blogs.mathworks.com\/graphics\/wp-json\/wp\/v2\/comments?post=249"}],"version-history":[{"count":5,"href":"https:\/\/blogs.mathworks.com\/graphics\/wp-json\/wp\/v2\/posts\/249\/revisions"}],"predecessor-version":[{"id":255,"href":"https:\/\/blogs.mathworks.com\/graphics\/wp-json\/wp\/v2\/posts\/249\/revisions\/255"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/blogs.mathworks.com\/graphics\/wp-json\/wp\/v2\/media\/254"}],"wp:attachment":[{"href":"https:\/\/blogs.mathworks.com\/graphics\/wp-json\/wp\/v2\/media?parent=249"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/blogs.mathworks.com\/graphics\/wp-json\/wp\/v2\/categories?post=249"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/blogs.mathworks.com\/graphics\/wp-json\/wp\/v2\/tags?post=249"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}