Link to home
Start Free TrialLog in
Avatar of PMH4514
PMH4514

asked on

Design critique question - UI performance

HI!

Maybe somebody can suggest some improvements for me.

I have a CStatic derived class called CDisplay and a CCamera base class that pushes frames, around 10fps to the display.  CCamera also has a CViewFinder base class that can alpahblend into the camera display, one of many "menus" implemented in CViewfinderMenu each containing "widgets" that I've implemented as CWidget.

So we have what amounts to menus and buttons, but they are graphical and blended directly into the video feed, as opposed to MFC dialogs and CButton's as they aren't static, they are painted (and the menu background alphablended) into a video stream.

So in my CDisplay::PreTranslateMessage()  (the only CStatic that can grab the messages) I check for mouse movement, and I forward the MSG* into CViewFinder. The CViewFinder will then forward the MSG* into each of its CViewfinderMenu instances, and the CViewFinderMenu will forward the MSG* into each of its CWidget instances. CWidget implements it's UI type stuff just like any button would, with normal, glow and pressed states.

Everything uses a CPtrList as the collection. the viewfinder has a CPtrList of menus, each menu has a CPtrList of widgets.

Each time I "forward the MSG pointer" it's just a matter of looping through, for example:


      POSITION pos = NULL;            
       for( pos = m_pWidgets->GetHeadPosition(); pos != NULL; )
            ((CWidget*)m_pWidgets->GetNext(pos))->CheckMessage(pMSG);


Anyway, everything does work as I'd expect, but it's a bit "sluggish" - not the video frame rate, rather, the speed at which my elements respond to the mouse messages.  I roll my mouse into the camera's video area, and the alphablended menus appear and my widgets glow and unglow as the mouse enters/leaves them,  but rather than a widget glowing immediately upon mouse entry, there is a delay, perhaps 1/2 a second (at most, but it's not immediate, and UI/visual responses need to be immediate)

At first I thought "well maybe it's because repaints are happening at the cameras frame rate" - but this model is implemented against two hardware devices, one at 10fps the other running at 30fps.. I don't perceive any difference in UI response regardless which feed is running, so I'm not sure if it's that.

Perhaps CPtrList is not the right container? would switching to a vector or deque improve performance? Maybe some kind of global lookup table of widgets so rather than constantly looping through everything I can look directly to a mouse postion?  Any suggestions on designs to improve UI performance here?

thanks!
-Paul

Avatar of wayside
wayside

Dumb question, as I'm sure you've done this - are you running a release version?

Howmany menus and widgets are we talking about? Unless you have hundreds or thousands of widgets, I doubt switching containers will make a noticeable difference.

It's really tough to say where the bottlenecks are without profiling the code. Do you have access to any performance measuring tools such as BoundsChecker?

What does the CheckMessage() function do?

How long does it take to paint the widget?

How much CPU is being used when no mouse activity is taking place?

One optimization that comes to mind would be for CViewFinderMenu to maintain a bounding box of all of its widgets. A quick check can then tell you if you need to send the message to any of the widgets in that menu. That would cut down a lot on how much message forwarding is going on.

Avatar of AndyAinscow
As wayside says run a release build.  Debug has loads of error checks that slows things down a lot.

Build menu - Profile.  (Follow instructions, look it up in help).  That will provide a lot of info as to what functions take the time up.  It is worth running. Rather than assuming a bit of your code is slow check - the bottleneck may not be where you think it is.
Avatar of PMH4514

ASKER

Hi guys -

yes, actually I did try on a release build. There is improvement as I'd expect, but there is still a delay, which if it wasn't pertaining to visual response to mouse, I wouldn't care, but I have to optimize it somehow.

We're not even talking about alot of menus.. A single 1000x1000 24bit image from the video stream will get two 400x400 rects alphablended onto it, and at most 8 "widgets" - each being a 30x30 bitmap resource..  I do all the widget painting into a memory DC once, the menu backgrounds of course have to get alphablended per frame, but everything else is pre-drawn an only blitted in place as the frames update.

CPU utilization is always kinda high, as I am streaming 2 video streams at once, one at 10fps, the other at 30fps, each 1000x1000..

But even when I turned one of them off, it's still a bit sluggish.

AndyAinscow - I'll checkout the Profile option.. It's disabled on my Build Menu. I'll look it up in help.
Avatar of PMH4514

ASKER

hmm.. I've enabled profiling in my link settings and rebuilt the probjec.. but Build\Profile is still grayed out ??
I think you need the professional or higher version of VC++ (my memory could be wrong here).  Have you got a standard/student version?
If it's disabled, I think that means you are running VC++ 6.0 Standard version. IIRC, you need the Professional version to get the profiler.
Avatar of PMH4514

ASKER

I'm running Enterprise Edition.. is "professional" higher?
No, enterprise is higher than pro.  It ought to be enabled on your build menu then.
Is the profiler an installation option?
You could always do "poor man's" profiling.

class Profiler {
   Profiler(const char *funcname);
   ~Profiler();

   private:
   const char *m_funcname;
   int m_start;
}

Profiler::Profiler(const char *funcname) {
   m_funcname = funcname;
   m_start = GetTickCount();
}

Profiler::~Profiler() {
   FILE *fp = fopen("c:/temp/time.log", "a+");
   if (fp) {
      fprintf(fp, "%s: start: %ld elasped: %ld\n", m_funcname,
                                                               m_start, GetTickCount()-m_start);
      fclose(fp);
   }
}

#define PROFILE_FUNC(x)
#if  PROFILE_MY_CODE == 1
Profiler prof(x);
#endif

Then at the top of each function you want to time, add:

PROFILE_FUNC(__FUNCTION__)

Add a define of PROFILE_MY_CODE=1 into the preprocessor options to turn on profiling. Set to zero to turn it off.

Run your program for a while, and then analyzet the log file to see where the time is going.
Avatar of PMH4514

ASKER

is that basically what profiler does wayside?

>>Is the profiler an installation option?
I'm not sure.. ??
> is that basically what profiler does wayside?

There are different ways of profiling.

One method typically modifies the assembly code to intercept function calls. It can then record information about what function it is in, who called it, timing information, etc. This can then be presented in a very nice UI that lets you see where all the time is going, and the calling paths into these functions. BoundsChecker works this way.

Another method is to interrupt the program every so often, say every 5 milliseconds, and recording what function you are in. At the end, by figuring out how many times you were in each function vs. the total number of samples taken, you can figure out how much time each function used relative to the others. This method doesn't give you insight into where the functions are being called from, though. I think the built-in profiler works like this, but my memory is hazy, I haven't used the built-in one in years (I don't even see it in VC++ .Net).

My crude method will give you actual elasped times, unlike the statistical data of method 2, but it also may not give you much insight into which code paths may be using all the time.
Avatar of PMH4514

ASKER

ok I'll give your profiler a try, see what I see.

Any other comments on the approach itself? using CPtrList vs. some other type of container? etc..
You have two menus with 8 widgets each?

I really don't think the container is the problem.

I wrote some code to compare the performance of CArray, vector, a static array and a dynamic array (https://www.experts-exchange.com/questions/20981128/CArray-slows-execution.html).

I used loops going through 50 million iterations, the worst performer took < 3 seconds. Scaling  down to 16 interations, the time would be on the order of 1 microsecond.

Using an array or vector would probably be quicker than a linked list, but you are still talking about microseconds for CPtrList. I'm betting the time is being spent in the repaint code.

Avatar of PMH4514

ASKER

>>I'm betting the time is being spent in the repaint code.

probably right.. I'll update when I've figured out something tangible.
Avatar of PMH4514

ASKER

nice loop tests by the way!
Avatar of PMH4514

ASKER

wayside:

I placed this in my stdafx.h file:

#include "Profiler.h"
#define PROFILE_FUNC(x)
#if  PROFILE_MY_CODE == 1
Profiler prof(x);
#endif


and I get a compiler error:

 error C2065: 'x' : undeclared identifier

ASKER CERTIFIED SOLUTION
Avatar of wayside
wayside

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
Avatar of PMH4514

ASKER

oh ok thanks.

in this:

      PROFILE_FUNC(__FUNCTION__);

at the top of any function I want to profile, is __FUNCTION__  a macro?

I'm getting:
error C2065: '__FUNCTION__' : undeclared identifier

Avatar of PMH4514

ASKER

oh ps. way above, wayside, you wrote:

>>What does the CheckMessage() function do?

that just does this:

      POSITION pos = NULL;          
      for( pos = m_pWidgets->GetHeadPosition(); pos != NULL; )
          ((CWidget*)m_pWidgets->GetNext(pos))->CheckMessage(pMSG);
Hmm, __FUNCTION__ must be a VC++ .Net -only macro, and not be defined in VC++ 6.0.

This makes it slightly more painful to use these, you will have to put the name in yourself in each function:

int MyClass::func1() {
  PROFILE_FUNC("MyClass::func1");
  ...
}

Avatar of PMH4514

ASKER

oh I see.

>>This makes it slightly more painful to use these,
oh well, there will only be a few I'll be checking

thanks!
SOLUTION
Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
Good point, Andy.

Also, note that because the timing is based on object construction/destruction, you can use this  anywhere you have a separate scope:

int MyClass::func1(){
  PROFILE_FUNC("func1 start")

...
  if (some_condition == true) {
     PROFILE_FUNC("func1 some_condition == true block")
  }
}

This would give you  a timing of both the entire function, and just that one specific block.
Avatar of PMH4514

ASKER

cool thanks.


interestingly... I changed the priority of the thread that is capturing video frames and pushing them through these filters to THREAD_PRIORITY_TIME_CRITICAL  and that made a substantial difference in how fast the UI responds..
 
I know threads aren't supposed to update the UI, but I think this is a slightly different case, what with no CStatic type window controls being involved.. since the "menus & buttons" are actually painted into each video frame, I could think of no other way
If I'm reading the docs right, that thread priority will boost your thread to higher than any other thread except those belonging to a realtime process. This could noticeably slow down everything else on your machine.

If you decide to boost your priority as part of your solution, you might want to experiment with different priorities to see what the lowest priority is that gives you the results you want.
Avatar of PMH4514

ASKER

>>This could noticeably slow down everything else on your machine.

That is probably OK though.. Our app/computer is integrated with our hardware, users won't be running other apps or anything.

it's not *the* solution, there is still some lag.. less, but still noticeable..
Thread priority is probably a hack.  Find out what bit of code is slow then look at how to optimise it.
Avatar of PMH4514

ASKER

>> Thread priority is probably a hack.  Find out what bit of code is slow then look at how to optimise it.
agreed.
Avatar of PMH4514

ASKER

update - I figured out why Profile was a greyed out option even though I had it installed and was running enterprise edition.

See this tech note:
http://support.microsoft.com/default.aspx?scid=kb;EN-US;Q224382