File Exchange Pick of the Week

progbar 6

Posted by Robert Bemis,

Bob's pick this week is progbar by Ben Mitch.

Do your MATLAB programs take a long time to run? Maybe you have a large simulation that loops over many iterations. It's nice to monitor progress. You can do that with waitbar.

h = waitbar(0,'Please wait...');
for step = 1:1000
  % computations take place here
  waitbar(step/1000)
  drawnow
end
close(h)

That works well for coarse loops with few iterations and considerable time spent during each. The instrumentation itself does add some overhead, however. As the number of iterations increases, so does the cumulative time.

loopSizes = [1 10 100 1000 10e3];
waitbarTimes = zeros(size(loopSizes));
for n=1:numel(loopSizes)
  myBar = waitbar(0,num2str(loopSizes(n)));
  myClock = tic;
  for k=1:loopSizes(n)
    waitbar(k/loopSizes(n),myBar)
    drawnow
  end %loop simulation
  waitbarTimes(n) = toc(myClock);
  close(myBar)
end %vary problem size
plot(loopSizes,waitbarTimes)
xlabel 'Loop Size'
ylabel 'Total Time (sec)'

Since a loop of ten thousand iterations added that many seconds of overhead, how many minutes would a loop of one million add?

100*waitbarTimes(end)/60 % 100X bigger loop (sec->min)
ans =
       41.332

Wow! If your whole simulation only takes a few minutes total to run then you probably don't want to bog it down that much by over instrumenting it.

You could add logic to your loop to only make updates every 10 iterations for example.

myBar = waitbar(0,'Big loop with coarse updates');
myClock = tic;
for step=1:1e4
  % computations take place here
  if mod(step,10)==0 %every 10-th iteration
    waitbar(step/1e4,myBar)
    drawnow
  end
end
toc(myClock)
close(myBar)
Elapsed time is 2.613291 seconds.

Of course now your scientific task involves some artistry to chose the right number of iterations to skip each time. However, suppose the amount of time varies widely between each iteration. This approach falls apart in that case.

What I like about progbar is that it features an update "period" setting you can control. The default is 0.1 seconds. So it will not update more than 10 times per second. Otherwise, it works (not exactly but) a lot like waitbar.

myBar = progbar;
myClock = tic;
for step=1:1e4
  % computations take place here
  progbar(myBar,100*step/1e4)
  drawnow
end
toc(myClock)
close(myBar)
Elapsed time is 3.375511 seconds.

If we vary the loop size again we can see where progbar begins to shine.

progbarTimes = zeros(size(loopSizes));
for n=1:numel(loopSizes)
  myBar = progbar;
  myClock = tic;
  for k=1:loopSizes(n)
    progbar(myBar,100*k/loopSizes(n))
    drawnow
  end %loop simulation
  progbarTimes(n) = toc(myClock);
  close(myBar)
end %vary problem size
plot(loopSizes,[waitbarTimes' progbarTimes'])
xlabel 'Loop Size'
ylabel 'Total Time (sec)'
legend waitbar progbar Location Northwest

Clearly progbar adds less overhead to our (hypothetical) simulation. Thanks, Ben!

Comments?


Get the MATLAB code

Published with MATLAB® 7.10

6 CommentsOldest to Newest

I often take an even more practical approach.

There is no point wasting time on a waitbar update when it won’t have a visible effect. Since the standard waitbar spans about 325 pixels on my screen, I use that fact to calculate an update frequency, as in the following example:

myBar = waitbar(0,'Update loop when effect will be visible');
myClock = tic;
nSteps = 1e5;
maxUpdates = 325; % number of pixels that span the waitbar
updateRate = min(nSteps,ceil(nSteps/maxUpdates));
for step=1:nSteps
    % computations take place here
    if ~mod(step,updateRate) % skip calls that won't have a visible effect
        waitbar(step/nSteps,myBar)
        drawnow
    end
end
toc(myClock)
close(myBar)

Joe, thanks for your comment. That approach is indeed pragmatic. More so than mine. But I think Ben’s automatic update-skipping has us both beat. :)

@Bob,

Thanks for the comment. Ben’s update-skipping is an excellent idea. Nevertheless, you won’t find me using his m-file for large loops because the many function calls to progbar take significantly more time than a simple math operation (~mod(a,b)). Try the following (which compares the 3 methods):

loopSizes = [1 10 100 1000 10e3];
waitbarTimes = zeros(size(loopSizes));
for n=1:numel(loopSizes)
    myBar = waitbar(0,num2str(loopSizes(n)));
    myClock = tic;
    for k=1:loopSizes(n)
        waitbar(k/loopSizes(n),myBar)
        drawnow
    end %loop simulation
    waitbarTimes(n) = toc(myClock);
    close(myBar)
end %vary problem size

maxUpdates = 325; % number of pixels that span the waitbar
updateFreq = min(loopSizes,ceil(loopSizes/maxUpdates));
waitbar2Times = zeros(size(loopSizes));
for n=1:numel(loopSizes)
    myBar = waitbar(0,num2str(loopSizes(n)));
    myClock = tic;
    for k=1:loopSizes(n)
        if ~mod(k,updateFreq(n)) % Limit # of waitbar calls
            waitbar(k/loopSizes(n),myBar)
            drawnow
        end
    end %loop simulation
    waitbar2Times(n) = toc(myClock);
    close(myBar)
end %vary problem size

progbarTimes = zeros(size(loopSizes));
for n=1:numel(loopSizes)
    myBar = progbar;
    myClock = tic;
    for k=1:loopSizes(n)
        progbar(myBar,100*k/loopSizes(n))
        drawnow
    end %loop simulation
    progbarTimes(n) = toc(myClock);
    close(myBar)
end %vary problem size

plot(loopSizes,[waitbarTimes' progbarTimes' waitbar2Times'])
xlabel 'Loop Size'
ylabel 'Total Time (sec)'
legend waitbar progbar waitbar2 Location Northwest

Now throw in a case where the loop size is 100,000:

progbar: Elapsed time is 19.004322
waitbar2: Elapsed time is 0.737003

Joe, fantastic. Now to make it even more interesting, and arguably more fair, move the drawnow for case #2 “outside” the IF-block and look at the graphs again.

We may have taken different viewpoints here. I failed to clarify earlier but I aim for code readability first followed by performance if needed. I see Ben’s submission helping to do exactly that by keeping the loop light on instrumentation details so as to not distract from the (hypothetical) simulation at hand.

Thanks again for the thoughtful comments. Very much appreciated!

Joe,

I like your method quite a bit. The part that I like is the fact that the overhead is pretty much going to be the same regardless of the number of iterations. It’s only going to redraw 325 times.

Now, it would be great if you could create a waitbar-like function that would do the check inside. If I could call it just like MATLAB’s waitbar, then it would be so simple to use. The whole benefit of Ben’s code is that it does the hard work for you. If you do the same with yours, I am completely sold! After that, please post it on the File Exchange.

hi guys

i had a play around; i really ought to get on with my work, but…

baseline, running joe’s code to compare waitbar2 with progbar, i get (@100,000 iters) waitbar2/progbar times of 1.26/27.5 secs, and the plot shows that waitbar2 scales better (sub-linear vs. linear for progbar).

next, the test is not quite fair, as bob pointed out. neither waitbar nor progbar need drawnow, and the drawnow for progbar is getting called on every iter, regardless if there’s drawing to do. so i took them out, times change to 0.55/5.33.

at this stage, it seems to be a fair test, and progbar scales less well because of (as joe said) the function call cost.

i then tried adding joe’s smart skip-by-progress-amount to progbar, on the grounds that it’s presumably a bit faster than reading a timer. no measurable difference – the cost of progbar() is in the function call, and getting the bar-specific data from the figure userdata (which allows multiple progbars to be open at once).

i also tried skipping updates to progbar globally (i.e. to all open progbars) on the basis of a 0.1 second period. this does speed things up a bit (since we only have to access a global variable, not the userdata of a figure), giving 0.55/2.36, but the downside is that if you DO have multiple bars open, you might well miss updates to the one that is updated less often because the one that is updated more often is likely to get given most of the update periods. given that it only gets twice as fast, i don’t think that’s worth doing. unfortunately, can’t take joe’s approach here since different bars will have different states.

beyond that, i don’t think there’s any way to make any further inroads. skipping all update calls _altogether_ in progbar() brings the time down to 0.55/0.84, but the graph still shows the relentless linear scaling for progbar, of course (so 1e6 iters would be 0.55ish/8.4, etc.). as joe says, this is the underlying problem. whatever code we imagined to skip updates, this basic cost (8.4us per iteration) would not go away.

in general, i’m with bob in that i prefer clean code to fast code (up to a point, at least, see below). 55us ish per iter isn’t bad. another reason why i don’t much like having to sort out the skipping in caller-scope is that not all loops are for n=1:1:N; more complex loops mean more complex caller-scope code to skip updates, which is transparent if we leave it to time-based skipping in the update function.

i think, then, that it’s got to be horses for courses. i use progbar (or a modified time-skipping version of workbar sometimes, because it’s pretty if a bit slower and i’m a sucker for eye candy) during development. when it comes to actually deploying the code in anger, if the loop is really tight (which means, in this case, less than about 500us per iter), i generally stick in a hack that aspires to do what joe’s uber-neat code does so elegantly (i’m stealing that, by the way ;)), or i remove the instrumentation altogether. i think joe is right – when it comes to pure performance, you can’t do better than an arithmetic check in caller-scope. but i think bob is right – clean code rules. in m-script, i can’t think of any way to bring the twain together any closer than joe already has. anyone?

cheers

These postings are the author's and don't necessarily represent the opinions of MathWorks.