[.net] Using 'new' in long running loops.

Started by
4 comments, last by vermilion_wizard 15 years, 10 months ago
Hi everyone, I've got a question for you guys. I've been wondering something about the .NET runtime. In tutorials, be it the old managed directx and even XNA I always see people doing things like: 'Rectangle rect = new Rectangle()' among other data types inside their render loops, to be hit every frame. My question is this: Is creating new instances of objects inside a long running loop something to avoid? I'm mainly confused about how garbage collection handles this. Does it cause GC to collect more frequently? Does it have any performance logic behind it? Should I always try when possible to allocate simple things outside the loop whenever possible? I'm thinking these tutorials/examples are doing it for simplicity sake, but it's also sending the wrong message. Any feedback would be appreciated :)
Advertisement
The less allocation you do, the better. If you have objects that persist across frames, don't new them every frame.

The GC just runs whenever it runs. The more stuff there is to collect, the longer that process takes. So minimizing allocations is a sensible strategy for performance.

I know this is .Net, but in C++ and other "low level" languages we create special pools for frequently new'd objects. Basically it's a pre-allocated array of space for them, and when you new one of these special objects it actually just constructs them in that already allocated memory; when you delete them it just marks the memory as free. You get pretty astounding performance boosts from using such strategies. However, I don't know if such techniques are even possible in a managed language.

-me
Thank you.

C++ and friends is the reason why I was so confused (done most of my programming in c/c++). Doing things like these allocations every frame in this way is not something that makes a lot of sense, but I see it all the time in .NET code so I started to wonder if there was a reason behind it. I suppose it was just for simplicity of explaining things.

Thanks again :)
Quote:Original post by Flimflam
I'm thinking these tutorials/examples are doing it for simplicity sake, but it's also sending the wrong message.


You're probably right that they're doing it for simplicity, in that they don't want to obfuscate the main point with peripheral issues, but the approach isn't necessarily "wrong."

Here is a good overview of the .NET garbage collection algorithm from MSDN.

A few of the important points to take away is that allocations under .NET are extremely fast because the runtime maintains its own internal memory pool that can be allocated from by essentially incrementing a pointer. No request to the OS needs to be made unless you fully consume all of the readily-available space. Additionally, objects such as the Rectangle in your example, will be a Generation-0, or "short lived" object, which are optimized for fast collection. Only if the object survives Gen-0 collection will it be promoted to a less frequent but more impactful stage of garbage collection.

This is not to say that code should allocate all over the place without a care, but individual allocations do not carry the weight that they do in unmanaged languages. If you conceptually need new objects each frame, don't be afraid to do so, unless profiling has specifically revealed GC time as a bottleneck.

Quote:Original post by Palidine
However, I don't know if such techniques are even possible in a managed language.


They are, but because of the way the GC works, they're usually not necessary.
The article explicitly discourages doing manual object pooling, because the runtime is basically doing pooling for you already, and doing it manually can interfere with the collection algorithm.

As an aside, since you mentioned XNA, there are caveats to this. The GC of the runtime on the 360 is less sophisticated than the PC counterpart, and I believe is non-generational. In this case, collections are more impactful, but avoiding blatantly wasteful allocations such as large chains of operator overloads that return intermediate objects should help. In the extreme case, object pooling has been said to be a viable way to overcome the shortcomings of the GC on the 360 when dealing with pathologically numerous object creations like particle systems.
Quote:Original post by Palidine
The GC just runs whenever it runs. The more stuff there is to collect, the longer that process takes. So minimizing allocations is a sensible strategy for performance.

I don't know much about .NET, but the above is not generally true of GCs. The amount of work they have to do tends to depend on the amount of live data, not on the amount of garbage, unless the garbage needs to be finalized. This is why increasing the size of heap improves GC performance, since it doesn't need to be invoked as often.

Generational GCs can indeed be quite quick to collect short lived objects, matching performance of stack allocation.
You should keep in mind too that value type objects (structs) don't have any GC performance at all unless you box them. Rectangle in XNA is a struct, so it is stack allocated. Doing something like
Rectangle r = new Rectangle()

is just initializing r, the values from the new rectangle are copied over whatever memory was in r.

The same is not true for arrays. Rectangle[] is a reference type, so reallocating arrays adds memory pressure to the GC. Also casting structs to Object or an interface type causes them to be boxed, and allocated on the heap. So that is something to avoid if you want to reduce the pressure on the GC.

This topic is closed to new replies.

Advertisement