[.net] Help with message loop, MDX/C#

Started by
21 comments, last by thezbuffer 17 years, 12 months ago
Before everyone beats on poor old DoEvents please go and profile some code using the different styles of RenderLoop.

Bottom line you will see more GC0s. In a very constrained memory situation you might be unlucky and get some GC1s but if that's the case you either have very little memory to start with or your app is very memory hungry in which case the DoEvents overhead is the least of your problems. If you don't know what a GC0 or a GC1 is then read the advice at the end.

I've looked at Perfmon while running my MDX apps and as long as you keep the memory doing just GC0's then you will be spending less than 1% of your time in the GC. That's tiny - unless you are some kind of game programming guru you can usually improve perf more than 1% by fixing your algorithms and drawing less. If you start seeing GC1 or GC2 then you have other problems that the render loop alone probably won't fix. Now sure, my profiling has just been done on some small test apps so I'm not claiming 100% certainty but I've sat and watched framerates and perfmon traces and not seen anything significant. I'm also convinced that the bigger the app the less significant any DoEvents overhead becomes. Yes total allocations is higher - but memory doesn't actually wear out.

Sure Ricks Blog showed a 200000% increase.... when it was doing nothing else. At this point its just statistics. The rest of the stuff inside your render loop is going to take orders of magnitude more time than DoEvents therefore dwarfing any effect the actual call will have.

So yes OnIdle/Peek events is the most efficient memory and speed wise and IMO there's no real reason to avoid it. But if you are more comfortable using DoEvents and you are not writing Half Life 3 then really, really don't worry about it.

*NOTE: Last time we had this debate I was informed that there is/was a memory leak situation with DoEvents where sometimes memory does not get reclaimed until the application quits. I've tried to repro this and have never been able to, nor have I seen a .Net bug report so its either an edge case or something that got fixed from an early .Net version.
ZMan
Advertisement
Quote:Original post by Saruman
no no no no no no no no no

DO NOT USE: Application.DoEvents

Unless you want to have memory problems, performance issues, and overall beat on the GC for no apparent reason.


Guess you can't get it any clearer than that [smile] RenderTaget already pointed out why, but I can also confirm that the performance improvement *may* be very significant. You don't need to worry if it's a hack (it's the official way after all) and conceptually I think it's quite elegant.

Edit: ZMan obviously also has a point, but if you know the most efficient way, why not just go with that?

[Edited by - remigius on April 22, 2006 5:59:53 AM]
Rim van Wersch [ MDXInfo ] [ XNAInfo ] [ YouTube ] - Do yourself a favor and bookmark this excellent free online D3D/shader book!
Alright, thanks for all the info. I think for now, while I'm building the app, I'm going to leave it at DoEvents just to keep things simple and clean, and when I have some significant work done I'll switch it to AppIdle and see what kind of performance I get. It seems like it would be kind of a pain to get the timing done properly if the render loop won't be running while there are messages on the queue, but I'll see how it goes.
No matter which loop you use you have to 'pause' at some point to look at or remove messages otherwise you are a bad windows app. None of these render loops tries to do anything clever with messages other than remove/process them between frames. Even Halo/Half Life/Doom etc etc have to do it.
ZMan
I understand that, but with DoEvents(), the pausing is contained within the main loop, so you could more easily track the framerate, and such. Messages are handled once per frame, easy as pie. With AppIdle, who knows how long it takes for the app to process the messages? Seems like it would make framerate-independent tasks a chore.

EDIT: I'll just ask this here, to avoid spamming the boards... are arrays of events feasible? I think it would be useful for my Keyboard class. Something like this is what I'm looking for:

keyboard.KeyDown[(int)Key.W] += MoveForeward();

I haven't found anything on it, so I'm not sure if it's possible, but someone here ought to know.
I think you are confusing what AppIdle is. AppIdle is called by the main thread when no messages are being processed. Not when the CPU is idle. If you look at the AppIdle loop it sits in a tight loop calling Render (one render per frame) unless it sees something in the message queue in which case it drops out of the tight loop and allows the message to be processed. As soon as that message is done then Idle will be called again.

So in this case the 'pausing' is also contained within the loop except it only pauses when it needs to. The DoEvents (and its overhead) is called in the other loop whether its need or not (another reason this is the right way).

There's been lots of smart thinkers look at this during MDX's lifetime - its not going to get any better than this even though it shows up in the forums at least once a month with people who think they can improve on it.
ZMan
Quote:Original post by thezbuffer
I think you are confusing what AppIdle is. AppIdle is called by the main thread when no messages are being processed. Not when the CPU is idle. If you look at the AppIdle loop it sits in a tight loop calling Render (one render per frame) unless it sees something in the message queue in which case it drops out of the tight loop and allows the message to be processed. As soon as that message is done then Idle will be called again.


I understand how it works. My worry lies in timing. Since messages are handled behind the scenes as far as the programmer is concerned, how can you reliably time each frame? I mean, it's easy to time the Idle loop, but how are we to know how much time the application spent NOT idle, outside of the loop?

Quote:So in this case the 'pausing' is also contained within the loop except it only pauses when it needs to. The DoEvents (and its overhead) is called in the other loop whether its need or not (another reason this is the right way).


Couldn't that easily be fixed by something like, if( !AppStillIdle ) App.DoEvents();?

Not that it matters, I'll just go with AppIdle. I'm curious though as to how much of a performance increase AppIdle gives. I think I'll do some tests.

Hmm, average framerate for AppIdle was about 2870, average for DoEvents() was 2840, for a program that does nothing but clear the screen and draw a triangle. Curious thing was that AppIdle used about 400kb less memory. Although, I guess it's not that odd if the GC really is collecting more often.
Quote:Original post by nsto119
Since messages are handled behind the scenes as far as the programmer is concerned, how can you reliably time each frame? I mean, it's easy to time the Idle loop, but how are we to know how much time the application spent NOT idle, outside of the loop?

Because you time frames by checking the timer at the start of each frame and subtracting the value from the start of the last frame. There is no 'missed' time. Any time spent processing messages will be included in the frame that was rendered right before the message was processed.

Quote:Original post by nsto119
Couldn't that easily be fixed by something like, if( !AppStillIdle ) App.DoEvents();?

I'm not sure its a fix but you can use that if you want the extra memory overhead.

Quote:Original post by nsto119
Hmm, average framerate for AppIdle was about 2870, average for DoEvents() was 2840, for a program that does nothing but clear the screen and draw a triangle. Curious thing was that AppIdle used about 400kb less memory. Although, I guess it's not that odd if the GC really is collecting more often.

And thank you for proving my point - there is no significant difference and thats on an application that does nothing. Lets look at a real app with a bit of math here using your numbers.

OnIdle 2870fps = 0.348ms per frame
DoEvents 2840 = 0.352ms per frame
which is a difference of 0.004ms

Now lets assume your final release version game with all its optimising runs is targeted to 100fps. This means your total render time per frame is 10ms. The 'overhead' to perf is 0.004ms or 0.04% of the total.

But still use OnIdle... there's no point avoiding or arguing over something thats already been well researched and investigated. I just wanted to make the point that your app won't actually suck because you chose DoEvents and anything you read otherwise is, IMO right up there with the stolen kidneys as urban legends.
ZMan
I'm not trying to argue against using AppIdle, just questioning the things I don't understand about it. But it all makes sense now. Thanks for the help :)
Quote:But still use OnIdle... there's no point avoiding or arguing over something thats already been well researched and investigated. I just wanted to make the point that your app won't actually suck because you chose DoEvents


Quoted for truthosity (can't tell on the kidneys [smile]). Despite my efforts I was unable to reproduce any significant performance gain by switching from DoEvents to the OnIdle loop. After discussing this with ZMan, I think the performance gain I experienced was mostly due to cutting out the OnPaint/Invalidate form of looping. Since this is mostly associated with DoEvents, that should be where a good part of the legend comes from.
Rim van Wersch [ MDXInfo ] [ XNAInfo ] [ YouTube ] - Do yourself a favor and bookmark this excellent free online D3D/shader book!

This topic is closed to new replies.

Advertisement