Why does my program run faster if I click and hold the caption bar?


Sometimes, people discover that a long-running task runs faster if you hold down the mouse. How can that be?

This strange state of affairs typically results when a program is spending too much time updating its progress status and not enough time actually doing work. (In other words, the programmer messed up badly.) When you click and hold the mouse over the caption bar, the window manager waits for the next mouse message so it can determine whether you are clicking on the caption or attempting to drag. During this waiting, window painting is momentarily suppressed.

That’s why the program runs faster: No window painting means less CPU spent updating something faster than you can read it anyway. Let’s illustrate this with a sample program. Start with the new scratch program and make the following changes:

class RootWindow : public Window
{
public:
 virtual LPCTSTR ClassName() { return TEXT("Scratch"); }
 static RootWindow *Create();
 void PaintContent(PAINTSTRUCT *pps);
protected:
 LRESULT HandleMessage(UINT uMsg, WPARAM wParam, LPARAM lParam);
 LRESULT OnCreate();
 static DWORD CALLBACK ThreadProc(void *p);
private:
 HWND m_hwndChild;
 int m_value;
};

LRESULT RootWindow::OnCreate()
{
 QueueUserWorkItem(ThreadProc, this, WT_EXECUTELONGFUNCTION);
 return 0;
}

void RootWindow::PaintContent(PAINTSTRUCT *pps)
{
 TCHAR sz[256];
 int cch = wnsprintf(sz, 256, TEXT("%d"), m_value);
 ExtTextOut(pps->hdc, 0, 0, 0, &pps->rcPaint, sz, cch, 0);
}

DWORD RootWindow::ThreadProc(void *p)
{
 RootWindow*self = reinterpret_cast<RootWindow*>(p);
 for (int i = 0; i < 100000; i++) {
  self->m_value++;
  InvalidateRect(self->m_hwnd, NULL, NULL);
 }
 MessageBeep(-1);
 return 0;
}

This program fires up a background thread that counts up to 100,000 and invalidates the foreground window each time the value changes. Run it and watch how fast the numbers count up to 100,000. (I added a little beep when the loop is finished so you can judge the time by listening.)

Now run it again, but this time, click and hold the mouse on the title bar. Notice that the program beeps almost immediately: It ran faster when you held the mouse down. That’s because all the painting was suppressed by the maybe-a-drag-operation-is-in-progress that was triggered when you clicked and held the caption.

Updating the screen at every increment is clearly pointless because you’re incrementing far faster than the screen can refresh, not to mention far faster than the human eye can read it. As a rule of thumb, changing progress status faster than ten times per second is generally pointless. The effort you’re spending on the screen updates is wasted.

Let’s fix our sample program to update at most ten times per second. We will run a timer at 100ms which checks if anything has changed and repaints if so.

class RootWindow : public Window
{
 ...
 LONG m_fChanged;
};

DWORD RootWindow::ThreadProc(void *p)
{
 RootWindow*self = reinterpret_cast<RootWindow*>(p);
 for (int i = 0; i < 100000; i++) {
  self->m_value++;
  InterlockedCompareExchangeRelease(&m_fChanged, TRUE, FALSE);
 }
 MessageBeep(-1);
 return 0;
}

LRESULT RootWindow::OnCreate()
{
 QueueUserWorkItem(ThreadProc, this, WT_EXECUTELONGFUNCTION);
 SetTimer(m_hwnd, 1, 100, NULL);
 return 0;
}

LRESULT RootWindow::HandleMessage(
                          UINT uMsg, WPARAM wParam, LPARAM lParam)
{
  ...
  case WM_TIMER:
   switch (wParam) {
   case 1:
    if (InterlockedCompareExchangeAcquire(&m_fChanged,
                                                   FALSE, TRUE)) {
      if (m_value >= 100000) {
        KillTimer(m_hwnd, 1);
      }
      InvalidateRect(m_hwnd, NULL, FALSE);
    }
   }
   break;
   ...
}

Instead of updating the screen each time the counter changes value, we merely set a “hey, something changed” flag and check it on our timer. We set the flag with release semantics in the producer thread (because we want all pending stores to complete before the exchange occurs) and clear the flag with acquire semantics in the consumer thread (because we don’t want any future stores to be speculated ahead of the exchange).

Run the program again and notice that it counts all the way up to 100,000 instantly. Of course, that doesn’t really demonstrate the progress counter, so insert a Sleep(1); into the loop:

DWORD RootWindow::ThreadProc(void *p)
{
 RootWindow*self = reinterpret_cast<RootWindow*>(p);
 for (int i = 0; i < 100000; i++) {
  self->m_value++;
  InterlockedCompareExchangeRelease(&m_fChanged, TRUE, FALSE);
  Sleep(1);
 }
 MessageBeep(-1);
 return 0;
}

This slows down the loop enough that you can now see the values being incremented. It’s not the dizzying incrementing that you saw in the original version, but it’s fast enough that people will get the point.

The mechanism I used to pass information between the background and foreground thread assumed that background changes were comparatively frequent, so that the timer will nearly always see something worth doing. If you have a mix of fast and slow tasks, you could change the communication mechanism so that the timer shut itself off when it noticed that some time has elapsed with no changes. The background thread would then have to start the timer again when it resumed updating the value. I didn’t bother writing this more complicated version because it would just be a distraction from the point of the article.

Comments (46)
  1. 8 says:

    Suppose progress is undetermined (for instance when transferring a file of unknown size), how many milliseconds would you pass the progress bar control with PBM_SETMARQUEE?

  2. Stu says:

    I remember old install programs that used to display the name of every file being copied.

    I was thought the install ran faster when it was minimized, but I didn’t think that copying even a small file could possibly be quicker that painting the file name. Is it possible that a CD-ROM file copy could be faster than Windows’ font painting?

  3. James says:

    VTE (the terminal emulator behind gnome-terminal) recently had something similar done, so it updates 40 times/second max. http://mail.gnome.org/archives/performance-list/2006-February/msg00006.html

  4. 8 says:

    Font rendering has been improved too. Here’s a nice overview of everything new and better:

    http://www.gnome.org/~davyd/gnome-2-14/

  5. Miles Archer says:

    On VAX (VMS) if a system had a heavy load and was sluggish, it would seem to give your process more resources if you hit Ctrl-T frequently. Don’t know if this was mere superstition or if it really worked. It’s kind of the opposite of what this case, though.

  6. Adrian says:

    Miles:  VMS, like many timesharing systems, would give your process a small, temporary priority boost whenever there was an interaction (like typing).  Hitting Ctrl+T probably had no direct effect, but you might get a short priority boost which gives you a few more cycles for a second or two.

  7. ah!… the Windows copy has always had this problem then!

    I have noticed many times when I’m copying files, that when I click in some other window the "time left" would go, say to 15 minutes, if I click back in the "copying window" the times goes to 3 minutes, not only that, but the progress bar does seem to move faster

    is this related to the same thing?

  8. Graham Bradshaw says:

    @Eber…

    This might be because Windows gives a higher priority to the foreground window.

  9. asdf says:

    On my computer, programs that do (disk) IO with a large amount of data can be sped up noticeably if you change their priority to idle. Fortunately winrar has an option for this so I don’t have to keep manually setting it.

  10. Thanks for the explaination.  I have noticed that in the past with programs, but I assumed it was my imagination.  After all it didn’t make sense how this could make some programs faster.

  11. Isaac Lin says:

    I used to notice this while watching streaming video from ifilm.com (can’t remember if I was using RealPlayer or Windows Media Player, though). The video was quite smooth while holding down the mouse button over the caption bar, and unwatchable otherwise.

  12. Adrian says:

    Isn’t this advice contrary to some of your recent posts?  Haven’t you been recommending notification mechanisms rather than polling?

    I’ve always thought of InvalidateRect() as a notification mechansim.  It sets a flag that we need to do work when it’s convenient.  The WM_PAINT message is low priority, sent when there are no other messages in the queue.  Several InvalidateRect()s can happen before the paint message is sent.  WM_PAINTs shouldn’t happen at some outlandish rate.

    Can you offer a perspective that distinguishes this case (where polling is preferred) from other the other recent examples where you’ve argued against polling?

  13. oldnewthing says:

    Adrian: I discussed this in the last paragraph but focused on the main idea of "don’t update too frequently".

  14. ipoverscsi says:

    A related question is: why does my application run faster when I wiggle the mouse over the main window?  I once had an application that would run dog slow unless you wiggled the mouse over the main screen (the application was doing some animation).  We used to say we were turning the crank to make it go faster.

    [I already discussed this. -Raymond]
  15. Chris Adams says:

    There’s a handy OS X utility (Quartz Debug) which allows you to highlight (literally – the area being painted will be tinted yellow/red) repaints and particularly repaints which are identical to the previous contents of the painted region. Is there an equivalent for Windows?

  16. AnotherMatt says:

    Hey Raymond,

    Great article.  I haven’t noticed that programs run faster, but I have been wondering lately why it takes so long for a window to redraw itself when you hold down the mouse button as you bring it forward from behind another window.   Now I know!  Learn something new every day.

  17. ChrisR says:

    This reminds me of a contest Korby Parnell had regarding a Visual Source Safe window that was suffering from this (http://blogs.msdn.com/korbyp/archive/2004/08/31/223384.aspx).

    I submitted a sample project to make one of Source Safe’s windows think the left mouse button was being held down on it’s caption (and it actually worked!).  Korby did respond that he was going to send me the baseball, but I’m not sure what happened.

    Alas, it never did arrive.  It’s no big deal, I just thought it was an interesting challenge.

  18. ChrisR says:

    AnotherMatt:  Arrg!  I have wondered what caused that for a very long time…thanks for pointing it out!

  19. zzz says:

    Unrelated…

    Why is it that when I have direct ethernet between 2 win boxes, the copying speed (windows shares) is normal, ~10 MB/s with 100 Mbit NICs.

    Now I insert a switch in between, the speed goes to ~5 MB/s. However if I use FTP to transfer the files with the switch in between, the speed is normal, 10 MB/s again (100 Mbit).

    So that was totally unrelated but with all the smart people here, someone must have an idea for why is this so. I seriously doubt the switch is doing any less processing with the FTP packets since it’s very basic model.

  20. 8 says:

    What if you use the broadcast address for the transfer? I couldn’t quickly find a tool to do it, but you’ve got a compiler ;)

  21. Marc Bernard says:

    Now all you need to do is get Rico to add this tip to the .NET performance guide…  :)

    Marc

  22. James Risto says:

    Can the same speedup be had by covering the window with another app’s window? I guess my question really is … are paint messages sent if the window is totally covered?

  23. Wow… example of how not to update status in a GUI! The Old New Thing : Why does my program run faster if I click and hold the caption bar? Sometimes, people discover…

  24. thewebguy says:

    haha this is awesome. i remember doing this on my old 386, never knew why it worked until now..

  25. Hmmm, doesn’t seem to work on my dual G5 Mac…. no, wait, that’s because it’s already running fast enough, thanks to an intelligently-designed OS with NO memory leaks built-in by the "factory".

    LOLzors

  26. Maxim says:

    Totally logical…

    The last time I saw that on my windows machine is with WinRAR.

    When extracting >1Go files… it takes less time to extract when running in the background than when you display the progress bar.

  27. MacSmack says:

    Hey Macie,

    You’re g5 wasn’t running fast enuf so they switched it to an Intel. Time to upgrade~!

  28. Leion says:

    This means I  should minimise my programs to make it run faster….

  29. Joxean Koret says:

    This means I  should minimise my programs to make it run faster….

    No. This means the programmers needs to ignore the redraw events during a large operation. Me, as for myself, always ignore the redraw events or, at least, lock the window (LockWindow).

  30. Bond, James Bond 007 says:

    I say it is all in your heads.

  31. lulu says:

    What the hell is a "caption bar"??

  32. me was smarte then me fall on head. says:

    Lulu: the bar at the top that has the program name in it, right now it should say something like "The Old New Thing: Why does my program run faster if I click and hold the caption bar? – (the name of your browser)"

    On the right of it there are those minimize, maximize and close buttons.

  33. Jansen says:

    Well.. I foudn out somthing intresting… in Win95 and Win98, if you were trying to end a task.. and you REALLY wanted to end it (you know sometimes how you can click and it doesnt die) wel.. if you click the END TASK button… and hold the mouse button in.. It ALWAYS kills it.. it was like the magic remedy. My IT friends, and associates thought this was quite humourous and completely illogical. But then again it was software from Microsoft!!

  34. Frode Børli says:

    Shouldn’t Microsoft somehow remedy this; that is – when a window is redrawing x times faster than the monitor can refresh, windows should fix it (in one way or another)?

    Or even: somebody could make a "windows accellerator" that detect too frequent redraws and respond accordingly? This would actually be a performance booster that worked!?

    Frode

  35. Youssef H. Wardani says:

    I encountered a similar situation coding in Delphi and using TDBGrid.OnDrawColumnCell

    So now when I run any process that has a parent form, I use LockWindowUpdate before the process starts and after it is done.

    example:

    LockWindowUpdate (Self.Handle);

    //my code …

    LockWindowUpdate (0);

    [Remember, people, just because somebody posts a comment doesn’t mean they’re right. -Raymond]
  36. 8 says:

    >Can the same speedup be had by covering the window with

    >another app’s window? I guess my question really is …

    >are paint messages sent if the window is totally

    >covered?

    Yes, that should do it. Also try grabbing a screenshot of a (partially) covered window. It should be black in the places where it was covered (actually the user would expect a proper screenshot, but hey…)

    >Shouldn’t Microsoft somehow remedy this; that is –

    >when a window is redrawing x times faster than the

    >monitor can refresh, windows should fix it (in one

    >way or another)?

    >Or even: somebody could make a "windows accellerator"

    >that detect too frequent redraws and respond

    >accordingly? This would actually be a performance

    >booster that worked!?

    I think that’s a bad idea. It would cause overhead by itself, and the detected monitor refresh rate isn’t neccisarily the real monitor refresh rate. I’m typing this in VMware, which fixes the virual monitor refresh rate to 60Hz no matter what, but it’s all fake since there is no real monitor refresh rate in the VM. Whatever the actual monitor refresh rate is, is up to the host OS to determine.

    Doing so would also break benchmarking tools, which *have* to draw faster then the refresh rate, to come up with some interesting results that basically say "your PC is x times faster then average" with some nice drawings.

    You also don’t want programmers to get even lazier, like "well, Windows will fix that for me anyway, so let’s paint this in a while(1) loop". Those programmers should somehow be punished -_-‘.

  37. J.Marsch says:

    Raymond:

    Your post started me wondering about another situation that I see sometimes:

    Why is it that sometimes when an application appears to be locked up, moving the mouse over it will cause it to become responsive again?

    The best example that I can think of is Visual Studio 2005.

    (this can be a little maddening because it doesn’t repro 100 percent of the time, but here goes):

    Create a Web Project, and then try to debug it.  Often, VS will just seem to pause just at the point where IE should start up.  If you move your mouse over the VS window, it will become responsive again.  If you don’t, it can take over a minute to get your debugger, and sometimes IE will actually time out.

  38. Raymond writes an interesting post about how some programs run faster if you hold down the mouse.

    This…

  39. Ivan Minic says:

    Very interesting find!

  40. Coleman says:

    Raymond,

    Another way to handle this is by having the thread send a registered window message to the main window.  Then the message handler would be responsible for painting or whatever.  The message handler could also decide to only repaint every 10th message or some other algorithm.

  41. Neil says:

    So how many paints are actually generated by the 100000 invalidates? Not 100000, I hope?

  42. oldnewthing says:

    Neil: Add some instrumentation and find out. You don’t need to ask me.

Comments are closed.