Typically when optimising XNA XBOX 360 games garbage collection usually comes to the top of the pile. I posted earlier about how to use CLR Profiler to locate code which is generating garbage, and this post will demonstrate one method that I use to eliminate garbage generation on a frame-by-frame basis.
First off, when using ToString() to get a string representation of a number (float, int etc) it generates garbage. Usually when you are converting numbers to strings you are then passing that string to some mechanism which will show it on the screen. This could be a score, ammo remaining, players level etc.
One method to avoid this is to store a string instance alongside your number, and only when the number changes, you ToString() it into the string. This works great for slow-moving numbers such as player level, but not great for score or ammo since it’s very likely that it will change almost every frame anyway.
The method I prefer is to store a lookup table of a certain range of numbers, and use the provided number to fetch the appropriate string from the array when required. This has a downside, which is up-front memory allocation (couple of meg for 200,000 numbers) and you’re limited by the size of the table you allocate initially. However the upside is that you don’t need to clutter your classes with a string instance for each number you want to show and you also have no penalty if you show a constantly changing number every frame.
Since I’ve wrapped it up in a nice little extension method called ToStringSafe() I’ll just post a shortened version of the static Extension class:
public static class Extensions
{
private static string[] _numbers = new string[60000];
static Extensions()
{
for (int i = 0; i < _numbers.Length; i++)
{
_numbers[i] = i.ToString();
}
}
private const string Negative = "-";
private const string NaN = "NaN";
private const string Inf = "Inf";
private const string K = "K";
public static string ToStringSafe(this float value)
{
if (float.IsNaN(value)) return NaN;
if (value > _numbers.Length - 1)
{
return string.Concat(_numbers[(int)( value / 1000 )], K);
}
return value >= 0
? value > _numbers.Length - 1
? Inf
: _numbers[(int)value]
: string.Concat(Negative, _numbers[(int)value * -1]);
}
public static string ToStringSafe(this int value)
{
return value > _numbers.Length - 1
? string.Concat(_numbers[value / 1000], K)
: _numbers[value];
}
public static string ToStringSafe(this long value)
{
return value > _numbers.Length - 1
? string.Concat(_numbers[value / 1000], K)
: _numbers[value];
}
}
And also a sample usage:
_spriteBatch.DrawString(_font, (GC.GetTotalMemory(false) / 1024).ToStringSafe(), new Vector2(0,20f), Color.White);
Which generates zero garbage every frame. It’s a very crude method but very effective!
The only case that will generate a small amount of garbage is when ToStringSafe()’ing negative numbers, as the ‘-‘ character is concatenated.
Also, you might notice my ToStringSafe methods are aware of the lookup table boundary and return a default value for those cases. The overload for float is a bit of a special case, which is why there’s a bit more to it. First of all, it doesn’t show the fractional component but that has never been an issue for displaying floats to the user, since generally the user doesn’t care about 4.53234 for my scenarios.
I’m sure if you really wanted to support the fractional components I’m sure there’s a way of isolating that part of the number and ToStringSafe()’ing that in itself. Maybe :)
9611a80a-71dd-4666-ba3c-63425a2a396b|0|.0
Tags:
Sample,
XNA,
Garbage Collection,
Optimisation,
XBOX
Categories:
Samples |
XNA