[.net] Plug in Tom's Render Loop in a few seconds!

This topic is 3618 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

Recommended Posts

Hey My garbage collection timeline is now lovely and flat :) I was too lazy to put the whole of Tom's render loop in, so I'll describe my simpler way. It's just as good as Tom's original because DoEvents only ends up getting called like 5 times during the life of the application (obviously if you move your window about, this will increase, but to no worse than before) So this was my old render loop:
while (mainWindow.Created)
{
FullRender();
System.Windows.Forms.Application.DoEvents();
}
And this is my new render loop, done in just a few seconds:
while (mainWindow.Created)
{
while (AppStillIdle)
{
FullRender();
}
System.Diagnostics.Debug.WriteLine("Calling DoEvents.");
System.Windows.Forms.Application.DoEvents();
}

private bool AppStillIdle
{
get
{
SafeNativeMethods.Message msg;
return !SafeNativeMethods.PeekMessage(out msg, IntPtr.Zero, 0, 0, 0);
}
}

So simple to plug in! You could do it right now...I'll be kind and give you the native methods, it wasn't so easy to complete these, I had to have a look at the SDK framework to find WindowMessage:
internal static class SafeNativeMethods
{
/// <summary>Window messages</summary>
public enum WindowMessage : uint
{
// Misc messages
Destroy = 0x0002,
Close = 0x0010,
Quit = 0x0012,
Paint = 0x000F,
SetCursor = 0x0020,
ActivateApplication = 0x001C,
NonClientHitTest = 0x0084,
SystemCommand = 0x0112,
GetMinMax = 0x0024,

// Keyboard messages
KeyDown = 0x0100,
KeyUp = 0x0101,
Character = 0x0102,
SystemKeyDown = 0x0104,
SystemKeyUp = 0x0105,
SystemCharacter = 0x0106,

// Mouse messages
MouseMove = 0x0200,
LeftButtonDown = 0x0201,
LeftButtonUp = 0x0202,
LeftButtonDoubleClick = 0x0203,
RightButtonDown = 0x0204,
RightButtonUp = 0x0205,
RightButtonDoubleClick = 0x0206,
MiddleButtonDown = 0x0207,
MiddleButtonUp = 0x0208,
MiddleButtonDoubleClick = 0x0209,
MouseWheel = 0x020a,
XButtonDown = 0x020B,
XButtonUp = 0x020c,
XButtonDoubleClick = 0x020d,
MouseFirst = LeftButtonDown, // Skip mouse move, it happens a lot and there is another message for that
MouseLast = XButtonDoubleClick,

// Sizing
EnterSizeMove = 0x0231,
ExitSizeMove = 0x0232,
Size = 0x0005,

}

[System.Runtime.InteropServices.StructLayout(System.Runtime.InteropServices.LayoutKind.Sequential)]
public struct Message
{
public System.IntPtr hWnd;
public WindowMessage msg;
public System.IntPtr wParam;
public System.IntPtr lParam;
public uint time;
public System.Drawing.Point p;
}

[System.Security.SuppressUnmanagedCodeSecurity] // We won't use this maliciously
[System.Runtime.InteropServices.DllImport("User32.dll", CharSet = System.Runtime.InteropServices.CharSet.Auto)]
public static extern bool PeekMessage(out Message msg, System.IntPtr hWnd, uint messageFilterMin, uint messageFilterMax, uint flags);
}


Hope that helps!

Share on other sites
Well messages are sent whenever the mouse moves or a key/button is pressed, so DoEvents is called a lot more than 5 times.

Share on other sites
Note that it really needs to be:
while (AppStillIdle && mainWindow.Created) otherwise your app might not exit when exceptions are thrown from within.

True, but it's still better than before :) And using WndProc is not my idea of fun :p, but thanks for that feedback...I'll have to test it more and see.
I hypothesize but unless mouse messages are sent at 70 times per second it's still probably a benenfit...but yes, more testing.

Share on other sites
Application.DoEvents() suffers from a significant memory leak when run in a loop. Not to be negative, but Tom's way is better because it does not suffer from this leak, hence his efforts in trying to find an ideal render loop approach in the first place.

Cheers,
Graham

Share on other sites
Use Spy++ to see just what messages are being sent and processed.

:D

Share on other sites
If anyone has a link to the evidence for the DoEvents 'leak' I would love to see it. Everything Tom and Rick talked about (http://www.thezbuffer.com/articles/185.aspx for the history) talked about excessive memory allocations, but no leak.

I chatted to Graham in IRC and he thinks the leak was blogged about but I can't find any reference and the sample code I have using DoEvents doesn't show any sign of a leak in perfmon, though you can see the increase in allocations and GC0's). I would love to add evidence for a leak to my history, or show that theres isn't one before one more rumour about bad event loops is created. (though to be honest, why bother - just use the one that everyone agrees is best)

Share on other sites
I think the term Memory leak is quite misused regarding DoEvents() . Everyone, and I as well, sometimes use Memory leak when pointing to continuous memory allocations caused by DoEvents(). In my recent thread there's quite nice discussion about this topic. Of course, just to sum up things, DoEvents() do not leak the memory, rather keeps allocating memory resources during the life of the game(loop), althought it frees the memory at the end.

Share on other sites
Yes, to clarify, managed applications can only "leak" memory (in the standard sense) if you are consuming legacy components or doing unmanaged interop. So essentially, the managed app itself isn't leaking. Managed apps can "leak" memory, but that memory is reclaimed by the GC when the application closes.

On the other hand, the leak DoEvents() ties with is a constant 4kb allocation every 5 seconds. And since DoEvents() is generally called in a render loop, this allocation happens a lot. Aside from the "leak" it's also very bad practice to call DoEvents() to force any kind of event processing (including control refreshing\invalidating). So regardless of the classification of problems behind that method, it is advisable to to look to alternate solutions (such as the Application.Idle event).

Remember kids: "Application.DoEvents() is pure EVIL" :)

~Graham

Share on other sites
Quote:
 Original post by gwihlidalRemember kids: "Application.DoEvents() is pure EVIL" :)~Graham

Just out of curiousity, what would you recommend using inside of a tight loop(in an app, not a render / game loop) where you want to force the app's message pump? Using .Net 2.0 /SQL Server 2000 I've found that if I don't call Application.DoEvents() inside of a tight loop where I'm executing a SQL statement on each iteration of the loop, my SQL performance drops significantly. The apps I'm doing this in are more utility apps that are pretty much every once in a while type things, and don't run for more than a few hours continuously, so memory loss in these instances isn't that big of a deal. After reading your thoughts on DoEvents, I'm more curious about an alternative than anything.

Share on other sites
Well, the whole problem behind that situation is that the loop is causing the message pump to not relieve other messages (like SQL). In .NET 1.1 you would use the asynchronous programming model (like BeginInvoke), and while you can do this in 2.0, you can also use the new BackgroundWorker class for it.

~Graham

Share on other sites
Quote:
 Original post by MePHyst0 DoEvents() do not leak the memory, rather keeps allocating memory resources during the life of the game(loop), although it frees the memory at the end.

If the memory does not get freed until the program quits then that is the closest you can get to a leak in pure .Net (as Graham stated the more normal kind of leak requires interop to get memory that the CLR know nothing about). The allocations that are caused by DoEvents should get tidied up each time a GC is called.

If you are seeing allocations that don't go away until you quit then its more likely to be MDX event handling holding onto MDX objects after you think they have gone out of scope. See Toms blog http://blogs.msdn.com/tmiller/archive/2003/11/14/57531.aspx

Here's how I monitored it. I have an app that does nothing but clear the screen inside a DoEvents render loop. I run perfmon and watch the following counters for the instance of my application (don't watch the global instance becuase you will see stats from every CLR app on your machine)
.Net CLR Memory/#bytes in all collections
.Net CLR Memory/#total committed bytes
.Net CLR Memory/#total reserved bytes
.Net CLR Memory/Allocated Bytes/sec
.Net CLR Memory/Gen 0 heap size
.Net CLR Memory/Gen 1 heap size
.Net CLR Memory/Gen 2 heap size

When I run the empty render loop everything gets very quickly to a constant level and then doesn't increase. The Allocs/sec of course is far higher than you would like to see in a 'do nothing' loop

If you add the following declaration:
static ArrayList leak = new ArrayList();
and then this inside your render loop
leak.Add(new StringBuilder("This will leak memory quite quickly"));
(humor me - I know this isn't really leaking because I can still see the ArrayList, but imagine that leak was a private variable inside another class tht has no method to clear it then its close enough)

Then repeat the above experiment and you will see all of the managed heap counters slowly but surely rise onward and upward.

I'm open to being told I'm missing something or being an idiot so feel free to correct me.

If you add in the '#Gen 0 collections' you will see that the large allocations causes a lot of GC0s. (on my PCs its 2 per second). But to put this in perspective add in the '% Time in GC' counter and (again on my PC) you will see that there is less than 0.1% of CPU spent in GC even with this excessive memory allocation. That's such a tiny amount that unless you are writing Half Life 3 I suspect you can get it back by writing a smarter culling algorithm. Interestingly enough if you watch '% time in GC' when you have the 'leaky' code added you will see it jump up an order of magnitude. Seems odd right ? I'm just allocating a small string, tiny compared to the huge amounts that DoEvents creates... I suspect this is because the leak causes those strings to get promoted and therefore we are running far more of the slower Gen1 and Gen2's which is always a bad thing.

For the record, I'm not defending DoEvents, it is obviously not the most efficient loop and I therefore see no reason for anyone to use it. But if you are seeing an ever increasing heap size or other leak type behaviour then based on my observations its not DoEvents.

Also for the record - I *am* using .Net 2.0 right now so if there was a real leak in 1.1 then its been fixed.

Share on other sites
The rate at which the GC runs the compact\collection thread is obviously machine dependent. So certain machines will hardly seem much of a "leak" if the GC is firing like crazy (though obviously this is still a very bad thing for performance elsewhere), whereas a machine with a low run count on the GC will see much more "leaking" memory. The increase to the an eventually constant amount is because that is the ratio where the GC can keep up with the allocations.

Now, this allocation issue was noted back in 1.1 (early on) so there is a strong chance that it has been fixed in 2.0

Regardless, using DoEvents() is bad practice and there is no reason to use it (I've even been guilty of it on occasion, though now I know better :)

DoEvents() is a hack and shows a place where a little refactoring can go a long way. So regardless of whether the leak still exists or even if it doesn't, explicitly calling DoEvents() is frowned upon by the greater community :)

~Graham

Share on other sites
Cheers for replies bt can't read them ATM...

Just posting that I realized the loop should not be,
while (AppStillIdle){	FullRender();}

rather it should be
do{	FullRender();} while (AppStillIdle)

or whatever the correct syntax is. Sorry for the original mistake which led to much GC when the mouse is moved over the screen; the new code is much better and what I intended before.

I'll catch up with you guys l8r...

Share on other sites
Quote:
 Original post by DrGUICheers for replies bt can't read them ATM...Just posting that I realized the loop should not be,while (AppStillIdle){ FullRender();}rather it should bedo{ FullRender();} while (AppStillIdle)or whatever the correct syntax is. Sorry for the original mistake which led to much GC when the mouse is moved over the screen; the new code is much better and what I intended before.I'll catch up with you guys l8r...

btw, just to mention, that was the conclusion of the topic I posted the link to a few post before ;)

Share on other sites
Quote:
Original post by jakem3s90
Quote:
 Original post by gwihlidalRemember kids: "Application.DoEvents() is pure EVIL" :)~Graham

Just out of curiousity, what would you recommend using inside of a tight loop(in an app, not a render / game loop) where you want to force the app's message pump? Using .Net 2.0 /SQL Server 2000 I've found that if I don't call Application.DoEvents() inside of a tight loop where I'm executing a SQL statement on each iteration of the loop, my SQL performance drops significantly. The apps I'm doing this in are more utility apps that are pretty much every once in a while type things, and don't run for more than a few hours continuously, so memory loss in these instances isn't that big of a deal. After reading your thoughts on DoEvents, I'm more curious about an alternative than anything.

At the company I currently work for, we had this problem with ASP.NET and certain long running GIS operations like spatial queries and plume cloud modelling. Allowing the operation to run normally and block really killed ASP.NET's performance, drastically reducing the number of maximum requests before IIS just crapped out. We've had a lot of success with throwing long ops into seperate threads and pinging the webserver to check for when it's done. Though this obviously increases the total number of requests on the server, it still allows us to handle a larger number of users.

Share on other sites
What about avoiding this method entirely. Since I override the paint method of the control, I never have an explicit loop, nor do I have to call DoEvents(). I simply call invalidate after painting and set the style to ignore the Erase Background Windows Message.

Just my 2 cents.

Share on other sites
Quote:
Original post by capn_midnight
Quote:
Original post by jakem3s90
Quote:
 Original post by gwihlidalRemember kids: "Application.DoEvents() is pure EVIL" :)~Graham

Just out of curiousity, what would you recommend using inside of a tight loop(in an app, not a render / game loop) where you want to force the app's message pump? Using .Net 2.0 /SQL Server 2000 I've found that if I don't call Application.DoEvents() inside of a tight loop where I'm executing a SQL statement on each iteration of the loop, my SQL performance drops significantly. The apps I'm doing this in are more utility apps that are pretty much every once in a while type things, and don't run for more than a few hours continuously, so memory loss in these instances isn't that big of a deal. After reading your thoughts on DoEvents, I'm more curious about an alternative than anything.

At the company I currently work for, we had this problem with ASP.NET and certain long running GIS operations like spatial queries and plume cloud modelling. Allowing the operation to run normally and block really killed ASP.NET's performance, drastically reducing the number of maximum requests before IIS just crapped out. We've had a lot of success with throwing long ops into seperate threads and pinging the webserver to check for when it's done. Though this obviously increases the total number of requests on the server, it still allows us to handle a larger number of users.

Right! Don't get me wrong, I'll use threads and background processes for alot of things, but for some quick fix solutions (like one time hacks that have to be performed on massive amounts of data, and can't use SQL directly or would be a pain to use SQL directly), it's just faster to write a for loop, hook it up to a button and let it roll.

I just thought it was interesting that performance dropped for the app itself when running single threaded without allowing the processing of windows messages. I thought it was a SQL thing, but now it sounds like it's a .NET thing... Also, good info on IIS.. We always create threads for processes that take longer than a second or two on IIS sites, just to enhance the user experience, but it's good to know that there is a potential to kill IIS by not doing so. Good info!

Share on other sites
Quote:
 Original post by gwihlidalYes, to clarify, managed applications can only "leak" memory (in the standard sense) if you are consuming legacy components or doing unmanaged interop.

Not entirely true:

public class SomeClass{   public static event EventHandler SomethingHappened;   // ...}public class SomeOtherClass{   public SomeOtherClass()   {       SomeClass.SomethingHappened += this.OnSomethingHappened;   }   private void OnSomethingHappened(object sender, EventArgs args)   {      Console.WriteLine("Stuff happens");   }}

This isn't a theoretical example either - I once spent several days tracking down a memory leak in a .NET app that was caused by this exact scenario (every time a particular dialog was opened, the app consumed 60 megs of memory and never gave them back).

Share on other sites
Hey MePHyst0, I've just read that thread, oops I reinvented the wheel!

My problem with going to Tom's render loop in its entirety is that control no longer remains in one method, it now relies on events. It would, of course, be possible to refactor to incorporate this, but my method was really just a quick fix, as zbuffer said, I'm not making Halo 3 [lol]
I just did some profiling; thanks to my profiling over the last couple of days, the allocations line is perfectly flat, then when I did some intensive swirling of the mouse over the window (clicking as well!) there was a GC every 5 seconds (improvement over every 2 before profiling). Next I did a slightly more realistic mouse simulation (previous example is worse case) and only got a gen 0 collection every 25 seconds. For a quick hack, I think I've reaped significant benefits as with the old render loop it was worst case scenario all the time!

Note that I had a lot of trouble with Direct3D.EffectHandle. I dispose all my objects and don't use the Direct3D automatic reset handling, however EffectHandles are not disposable although they get finalized. That means they build up until a gen 1 collection. I was rather surprised at the problem since I already cache parameter handles (I'm not silly enough to use GetParameterBySemantic every frame!), but it turns out that just effect.Technique creates an effect handle. I solved this problem by basically doing even more caching. Note that I also cache the surfaces of my render-target textures else the GC gets flooded with Surfaces and EventHandlers.

Arild Fines, your problem is similar to the one described in the thread cited by thezbuffer, here. Essentially the event handlers hold a reference to the target class and therefore prevent it from being collected.

Share on other sites
Quote:
 Original post by Krisc...and set the style to ignore the Erase Background Windows Message...

That might be good to do, how do you do that? It doesn't seem to be a standard property of the form as seen in the properties window.

Cheers

Share on other sites
Quote:
 Original post by DrGUI For a quick hack, I think I've reaped significant benefits as with the old render loop it was worst case scenario all the time!
Ironically, it's no longer a quick hack after you do all that profiling and tweaking.

Share on other sites
Quote:
Original post by DrGUI
Quote:
 Original post by Krisc...and set the style to ignore the Erase Background Windows Message...

That might be good to do, how do you do that? It doesn't seem to be a standard property of the form as seen in the properties window.

Cheers

Generally if you know the control you can just call the .Setstyle method for it, but since Setstyle is a private/protected function you may have to use reflection to get at it. You can also use subclassing to block the Windows Message from being used. This is how I used reflection, where m_control is the control that the user wants to draw to.

try{    Type type = m_control.GetType();    BindingFlags flags = BindingFlags.NonPublic | BindingFlags.Instance;    MethodInfo method = type.GetMethod("Setstyle", flags);    if (method != null)    {        object[] param = {Controlstyles.AllPaintingInWmPaint | Controlstyles.Opaque, true};        method.Invoke(m_control, param);    }}catch{}

Share on other sites
Quote:
Original post by Arild Fines
Quote:
 Original post by gwihlidalYes, to clarify, managed applications can only "leak" memory (in the standard sense) if you are consuming legacy components or doing unmanaged interop.

Not entirely true:

*** Source Snippet Removed ***
This isn't a theoretical example either - I once spent several days tracking down a memory leak in a .NET app that was caused by this exact scenario (every time a particular dialog was opened, the app consumed 60 megs of memory and never gave them back).

I meant leaks "in the standard sense", like memory that cannot be reclaimed by the application. You have shown me, though a totally valid example of a managed leak, nothing showing a leak in the standard sense. That memory will be reclaimed as soon as the static event in SomeClass is released.

I think the original "leak" with DoEvents was related to static delegate assignment after looking at some disassembled code, though I can't be entirely sure.

~Graham

Share on other sites
Quote:
Original post by capn_midnight
Quote:
 Original post by DrGUI For a quick hack, I think I've reaped significant benefits as with the old render loop it was worst case scenario all the time!
Ironically, it's no longer a quick hack after you do all that profiling and tweaking.

Yeah; that reminds me of that song... 'he'd saved his whole damn life, to take that flight, and as the plane crashed down, he thought well isn't this nice? Isn't it ironic, don't cha think?' [lol]

Seriously though, I came up with that tweak as I was delivering to the 2nd house in my paper round (an occupation which does not require a brain) and I was profiling my allocations anyway. I probably spent more time reading and replying to this thread [lol] Oh teh noes I'm spending time on this thread again! Run away!

Share on other sites
btw, I tried to profile one of the managed DirectX samples but the profiler just had like 'Handle Allocations: 0', Gen 0 heap size: Unknown' etc. Does that mean you can do something to stop PIX profiling your app?

Share on other sites

This topic is 3618 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

Create an account

Register a new account

• Forum Statistics

• Total Topics
628710
• Total Posts
2984319

• 23
• 11
• 9
• 13
• 14