Chapter 4: Performance

This chapter contains general tips about improving performance of your games written with AgateLib.

This page should be read with the thought in mind, premature optimization is the root of all evil. Don't bother optimizing your code until you know it's too slow, or it's done. And don't optimize code before running through a profiler. Programmers are usually not very good at predicting which parts of their code will actually be performance bottlenecks. Compilers can be quite surprising in the things they will and won't be able to optimize efficiently.

NProf is an excellent free open-source .NET profiler. NProf will give you an indication of what parts of your application take the most CPU time.

The CLR Profiler will profile the memory usage of your application. It will tell you when, where, and how much of each type is allocated, how often the garbage collector runs, how much is collected and much more.

MSDN Blogs

Here's some interesting .NET related blogs from people at Microsoft I've found. Many of these contain some very useful performance tips.

Garbage Collection

Garbage collection can be a serious enemy of writing smoothly performing games. A collection which lasts 50 milliseconds is virtually unnoticable in a desktop application, but in a game running 60 frames per second, this results in 3 dropped frames. The implications go beyond that; it means the amount of time between the frame before the GC and the next frame is four times what it usually is, which causes objects to appear to jump across the screen in a very jerky manner and is quite unattractive to the user.

On the other hand, there are a number of performance advantages that garbage collection grants. It automatically compacts memory, so memory does not become fragmented over time, and a small enough application can maintain a large amount of its data in cache memory, avoiding costly cache misses.

The old adage goes, ''the fastest code is code that never runs.'' To make an analogous statement, we could say from that ''the fastest memory to garbage collect is that which was never allocated.'' Some performance strategies I've had success with are:

  • Avoid using the new operator on reference types (classes) during every render loop. Preallocate these objects, and provide methods to reinitialize their private data if need be.
  • Avoid boxing. This is where you take a value type and pass it to a method expecting an object. Use generic types and overloads where possible for this. Avoid using ArrayList objects, use List instead.
  • Avoid passing value types (structs) to methods requiring an interface. This causes boxing to occur.
  • System.DateTime.Now seems to do a lot of boxing of ints. The methods in ERY.AgateLib.Timing (attempt to) use platform specific calls to high resolution system timers. These should be better in terms of allocations than DateTime.Now.

Loading levels

Contrary to the usual wisdom, if you are loading a new level or map in your game, it's often a good idea to throw in a GC.Collect(); at the end of your load routine. If the user spends a fair amount of time on your level, then most of the objects specific to the level will probably have made it to generation 2, which is a garbage collection that takes a fair amount of time to execute. When loading a new level, many of those objects are unreferenced, and subject to collection. But you don't want to see a Gen 2 collection a few seconds after the user starts the next level, this will be really annoying to them, because it will cause a sudden freezing of your application for a brief moment. Throwing a GC.Collect() after loading a new level and making sure to dereference all the old data forces the garbage collection to occur at a time when the user expects to be waiting anyway.

You should not throw in GC.Collect anywhere until you are certain that you have a garbage collection problem. You can see if you have garbage collection issues by using the CLR profiler.