Storage Informer
Storage Informer

.Net performance and Xna

by on Jul.19, 2010, under Storage

.Net performance and Xna

EMC logo

I was recently involved in a thread called “What are typical pitfalls when writing games with managed Languages like C# or Java?” over at the game dev stack exchange, and i think it might be interesting to archive my reply over here. Having spent an awful lot of time programming Xna/C#, I’ve learned a fair few things over the years that may be of help, or at least food for thought.

For a bit of background, real time 3D apps need to be fast. 60 frames a second is a good target to hit for smooth animations, leaving us with 16.6 milliseconds to do that frame. Everything, all the update logic, physics, AI, culling and rendering. It’s not a long time.

One of the biggest performance problems for Xna is the garbage collector. The .net garbage collector on windows does a fantastic job, and you can get away without baby sitting it for the most part. On the xbox/winPhone7 its a different matter. A cursory look at the creators club forums for posts about garbage collection reveal that average collection times can be upward of 85 milliseconds. Sounds insignificant, but that does mean 5+ skipped frames. The compact framework (use on winPhone7 and xbox among other devices) currently triggers a collection after every 1mb allocation. What does that mean for us? Don’t allocate more than a meg when your app is running!

Here’s some tips for dealing with garbage. You shouldn’t have to worry about most of these in reality, but they may come in handy one day.

  • Draw the contents of GC.GetTotalMemory() to the screen. This gives you an approximation of the amount of allocated bytes your app uses. If it hardly moves, garbage isnt the root cause of your problems. If its going up fast, you have issues.
  • Try to allocate all your heap objects up front. No allocations, no collections. as simple as that.
  • After loading, call GC.Collect(). If you know most of your big allocations are out the way, its only nice to let the system know.
  • DO NOT CALL GC.Collect() every frame. It might seem like a good idea, keeping on top of your garbage and all that, but remember the only with worse than a garbage collection is over garbage collection.
  • Look for where your garbage is coming from. There are some common causes like concatenating strings instead of using StringBuilder (be where, StringBuilder isn’t a magic bullet, and can still cause allocations!. This means simple operations like adding a number to the end of a string can create surprising amounts of garbage.) or using foreach loops over collections that use the IEnumerable interface can also create garbage without you knowing (for example, foreach (EffectPass pass in effect.CurrentTechnique.Passes) is a common one)
  • Use tools like the CLR memory profiler to figure out where memory is being allocated. There are loads of tutorials out there on how to use this tool.
  • When you know where your allocating during gameplay, see if you can use tricks like object pooling objects to lower that allocation count.
  • If all else fails, make your collections run faster! The GC on the compact framework follows every reference in your code to figure out what objects are not in use anymore. Refractor your code use less object references!
  • Remember to use IDisposable on classes that contain unmanaged resources. You can use these to clean up memory the GC cannot free up itself.

Garbage collection isn’t the end of memory woes. Real time code tends to have really tight loops that run hundreds, maybe thousands of times a frame. Lets have a look at an example.

public Matrix GetBodyMatrix()             {                 return Matrix.CreateRotationZ(rotation) * Matrix.CreateTranslation(position.X, position.Y, 0);             }

            public Vector2 GetWorldPosition(Vector2 localPosition)             {                 return Vector2.Transform(localPosition, GetBodyMatrix());             }

This snippet is based on a chunk of code from the farseer 2D physics engine. We don’t care at all about its function, we care about all the local struts its declaring. Structs, like Xna’s Matrix and Vector2 live in the stack, and allocating stack memory is incredibly cheap, its basically incrementing a pointer (discussing exactly how the heap/stack work are out of scope for this article, there lots of good stuff on the net to help). So, a call to GetWorldPosition allocates 4 matrices (a RotationZ and translation, which are concatenated in to a third which is copied as the return to the calling method) and 1 Vector2. One call will allocate 66 floats on the stack. This method is called thousands of times in the physics engines loop, so farseer’s developers have optimised it to look like this.

private Vector2 _worldPositionTemp = Vector2.Zero;

private Matrix _bodyMatrixTemp = Matrix.Identity; private Matrix _rotationMatrixTemp = Matrix.Identity; private Matrix _translationMatrixTemp = Matrix.Identity;

public void GetBodyMatrix(out Matrix bodyMatrix) {     Matrix.CreateTranslation(position.X, position.Y, 0, out _translationMatrixTemp);     Matrix.CreateRotationZ(rotation, out _rotationMatrixTemp);     Matrix.Multiply(ref _rotationMatrixTemp, ref _translationMatrixTemp, out bodyMatrix); }

public Vector2 GetWorldPosition(Vector2 localPosition) {     GetBodyMatrix(out _bodyMatrixTemp);     Vector2.Transform(ref localPosition, ref _bodyMatrixTemp, out _worldPositionTemp);     return _worldPositionTemp; }

This seems a little strange at first. All the local variables have been promoted to class members, meaning they will now live in the heap. They also use the ref/out versions of all the maths methods they were using. This means the struts are passed by memory reference, instead of being copied between methods. The big question is why does this help? The allocations to the stack might be basically free, but initializing these types are not. Each call to that method had to initialise 66 floats to 0. As crazy as it sounds, this optimised version can run ~10-15% faster. 

Update your feed preferences

URL: http://emcfeeds.emc.com/l?s=100003s2f6pa6831qks&r=rss2email&he=68747470253341253246253246636f6e73756c74696e67626c6f67732e656d632e636f6d25324674696d6a616d65732532466172636869766525324632303130253246303725324631392532466e65742d706572666f726d616e63652d616e642d786e612e61737078&i=70726f78793a65383437633065372d333864392d343563302d623539332d3536373437333033653038383a3137353036


Leave a Reply

Powered by WP Hashcash

Looking for something?

Use the form below to search the site:

Still not finding what you're looking for? Drop a comment on a post or contact us so we can take care of it!

Visit our friends!

A few highly recommended friends...