Monday, February 1, 2010

Remote Performance Monitor

Performance is critical in game development. In XNA, game performance will degrade if you allow garbage to be generated during game play. On Xbox 360, generating too much garbage will force full garbage collection. If garbage collection takes longer than 1/60th of a second then the game will drop frames, and the more frequently full garbage collection occurs, the more frequently the game will drop frames.

The XNA Framework Remote Performance Monitor is a simple tool that can detect if game code is generating too much garbage. Here is a common work flow to game development and performance testing using the Remote Performance Monitor:
  • Write game code on Windows
  • Test game play on Windows
  • Deploy game code to Xbox 360
  • Launch Remote Performance Monitor
  • Start game from Remote Performance Monitor
  • Monitor performance results
  • Resolve performance issues as necessary
  • Repeat process

Here is a quick tutorial on how to use the XNA Framework Remote Performance Monitor if you have never used the tool before.

There is a large source of information available on the Internet, in the form of blog posts, audio casts, articles and white papers that gives detailed analysis on all the statistics generated from the Remote Performance Monitor; from Pinned Objects to Platform Invoke Calls.

However, in my experience, there are typically 3x statistics that require the most attention during performance testing:
  • Managed String Objects Allocated
  • Managed Objects Allocated
  • Boxed Value Types

Ideally, the goal is to have the "Delta" column in the Remote Performance Monitor for these 3x statistics consistently set to zero during game play:
This ensures game code does not unnecessarily generate garbage, force full collections and drop frames.

Unfortunately, during game development on the XNA platform, there appears to be 2x common game code scenarios that cause the "Delta" column in the Remote Performance Monitor to be consistently set to values greater than zero during game play:
  1. Unnecessary string object creation
  2. Unnecessary boxed value types

Each scenario reveals game code that consistently generates too much garbage, impacts performance and has the potential to drop frames.

Let's check out each scenario in greater detail:

Unnecessary String Object Creation
In a typical game, there is often a lot of numeric data that must be displayed on screen to the player, for example: score, hi score, level, lives, bonus etc. Consequently, there are numerous game code snippets similar to the following:
public void Draw()
{
 spriteBatch.DrawString(spriteFont, score.ToString(), position, color);
}
Each time game code executes score.ToString(), the .NET Framework will allocate a single managed string object on the heap. When game code executes score.ToString() unconditionally at 60fps then 60x additional managed string objects will be allocated accordingly every second:


However, there is no reason for game code to execute score.ToString() unconditionally every single frame.

A better approach would be to create 2x variables: one variable to store the integer score value and another variable to store the equivalent string representation of the score:
private Int32 scoreValue;
private String scoreText;
Now game code would only be required to execute score.ToString() when the score actually changed:
public void Update()
{
 if (playerKilledSomething)
 {
  scoreValue += 100;
  scoreText = scoreValue.ToString();
 }
}
public void Draw()
{
 spriteBatch.DrawString(spriteFont, scoreText, position, color);
}
During a standard frame, in which the score value will not change, the "Delta" column in the Remote Performance Monitor for Managed String Objects Allocated will now be set to zero as no garbage generation occurs:


This simple approach to avoid unnecessary string creation may seem obvious, but it is surprising how many times the following game code can be found in Production:
public void Draw()
{
 spriteBatch.DrawString(spriteFont1, score.ToString(), position1, color1);
 spriteBatch.DrawString(spriteFont2, hiScore.ToString(), position2, color2);
 spriteBatch.DrawString(spriteFont3, level.ToString(), position3, color3);
 spriteBatch.DrawString(spriteFont4, lives.ToString(), position4, color4);
 spriteBatch.DrawString(spriteFont5, bonus.ToString(), position5, color5);
 // continue draw method...
}
In the next post, we will continue this discussion on the Remote Performance Monitor with unnecessary boxed value types.