UI Updating can be hazardous to your performance…


Dare writes about a perf and memory usage issue in RSS Bandit.


I've come across this at least 10 times in my career - developers tend to underestimate the amount of time it takes to update a UI. I've seen cases where 70% of the cpu time is going towards updating the UI.


There are two classical fixes for this:



  1. Updating every <n> items. This gives you 90% of the gain possible, and it very simple to implement.

  2. If the updates are chunky - ie sometimes you get 100 per second and sometimes you get 2 per second - a time-based approach works better. Don't make an update until at least a specific interval has passed since the last one. I generally use 250 milliseconds for the interval.

 


Comments (13)

  1. CN says:

    I even remember a codebase where a certain operation was quite slow. In it was a line like this:

    if (itemCount % 16) updateGui();

    (this was in C++, so an int to bool implicit cast was done)

    As you might imagine, inverting that condition boosted performance quite a lot.

  2. Drew says:

    CN: Inverting that condition? Meaning update for 15 of every 16 items added? The original code was doing what Eric suggested (not updating for every change, but sort of "batching" changes), though evidently the timing wasn’t right in your case. And why bother skipping that 1 of every 16? If the perf in your was better when updating on 15 out of 16 item adds it probably would have been better still to remove that condition altogether.

  3. Mihailik says:

    I have simple and well-behaving code for this purpose.

    DateTime nextUpdate = DateTime.Now;

    while( <cycle> )

    {

    // … working

    if( DateTime.Now>=nextUpdate )

    {

    // … update UI

    nextUpdate = DateTime.Now + TimeSpan.FromSeconds( 0.3 );

    }

    }

  4. PatriotB says:

    Inverting that condition is what he meant. Look closely at the line:

    if (itemCount % 16) updateGui();

    The way it’s written it will update 15 out of 16 times. Inverting it will do one out of 16:

    if (!(itemCount % 16)) updateGui();

    This will cause updateGui to only be called when (itemCount % 16) is zero.

  5. damien morton says:

    What you want to do is trigger a timer to fire off x milliseconds after a change has happened. This means that if no changes happen, no updates are triggered, and that the maximum update rate will be x milliseconds.

  6. G. Dog says:

    Damien is correct, the right solution is to use a watchdog timer that fires and updates the GUI. Every time a backend change happens you reset the timer. This is the ideal solution in all cases.

  7. damien morton says:

    Oh yeah – one other thing… GUI updating wouldnt be so much of a problem if GDI+ wasnt so dog slow.

    For basic things like rendering text, GDI+ is nearly an order of magnitude slower than GDI. As a result, you end up having to be much more clever about arranging incremental updates to the screen.

    GDI+ performance hasnt improved in Whidbey beta 2. I have no idea what text rendering performance is like under Avalon. I cant imagine that the GDI+ rendering model will ever be hardware accelerated – its just too complex.

  8. mihailik says:

    Damien, your solution is wrong.

    If you run long-running task and timer callback on 2 different threads, you have to battle race conditions. Measurement will be much harder and complex to implement than primary work itself.

    If your timer will be run on UI thread (e.g. WinForms timer), you will get no timer hits until work is done completely. It means your progress bar will show 0%, wait, and then 100%.

    So using timers for progress indication is not productive.

  9. mihailik says:

    1. Whidbey and GDI.

    There are cool tools in Whidbey, that particularly allows GDI text rendering. See TextRenderer class and VisualStyles namespace. These facility is system-supplied renderer set for many kinds of controls.

    2. GDI vs GDI+.

    I agree, GDI+ is noticeable slower than GDI. But I believe GDI+ speed is acceptable for most GUI tasks. I have seen enough examples of GDI+ animated UIs.

  10. damien morton says:

    mihalik – perhaps I miscommunicated the time-based approach. It does work, and it works in a multithreaded environment. The actual mechanism use to implement this technique (thread.timer, form.timer) depends on the situation. The basic algorithm, that you should start a ‘timer’ when you get a change, then ignore subsequent changes until the ‘timer’ expires, after which you update and return to the initial state, is better than some of the alternatives (i.e. updates to every x millisecs).

    When I was testing Whidbey text rendering, I’m pretty sure I tried TextRenderer. Cant remember the exact results, but broadly, nothing has changed in Whidbey as far a text rendering speed goes. Of course, I was testing on an early version of Whidbey. I could try again.

    The problem is that the GDI+ rendering model is fundamentally a vector rendering model, which is great for doing fancy effects, but terrible for blasting out simple axis-aligned text. Even a high-end 3D graphics card wouldnt really help GDI+, because it supports a different rendering model. For that reason, I dont expect Avalon to have decent text rendering performance.

    "GDI+ speed is acceptable for most GUI tasks". Hmm, yeah, except when it isnt. GDI+ can fill a 1024×768 screen with text approximately 6-8 times a second. GDI can do it 40-60 times a second. If you’re writing a text editor, and you want to have fast page scrolling, you need a decent text renderer, and hardware acceleration helps. In the absence of fast text rendering, theres things you can do, i.e. by blitting the on-screen text around instead of re-rendering the whole page, but fundamentally, you are just tweaking the performance curve in some ‘common’ use cases, at the expense of enormous programmer effort.

    For the GUI tasks I work on, which involve blasting out real-time data into grid and graphs onscreen, simple text rendering can consume over 50% of CPU, even when gating updates to 250ms, and intelligently managing screen invalidations. Its a struggle.

  11. damien morton says:

    Hmm, juts found a document which describes the text rendering architecture in Avalon. It seems that if you have DX10 capable graphics hardware, then you will see a 10x improvment in text rendering performance from 100,000 to 1,000,000 glyphs per second.

    On my 1280×1024 screen, I can fit about 150*70 == 10,000 characters on a screen, which corresponds to the 10 frames per second refresh rate Ive been seeing with GDI+. With DX10 hardware, a 100fps rate could be achieved. Thats fast text rendering.

    That said, Avalon and DX10 hardware is several years off.

Skip to main content