Using CLRProfiler to Take Out the Trash, Part 2


In last week’s post, I described how I used CLRProfiler to identify a couple parts of my code that were producing garbage every frame. One related to equality comparisons with my ActorState struct and another involving System.Objects being produced when I called RuntimePropertyInfo.SetValue(). This time, I’ll talk about what I did to fix those two problems.

Fixing Problem #1: Structs as Dictionary Keys
So, according to the CLRProfiler, I’m creating ActorStates via Generic.ObjectEqualityComparer::Equals() during my ChooseTargetNode() function. Looking at that code, it simple looks like I’m checking against a Dictionary that uses ActorState as a key. A quick Google search turns up this tidbit on XNA Wikia. The article talks about how Enums cause memory allocations when used as keys in Dictionaries. It’s fair to assume that structs would have similar behavior and cause garbage to be created when used by the Dictionary’s internal comparison functions. Luckily, the fix for this is easy. If a struct implements IEquatable, Dictionary won’t allocate instances of that struct during comparisons. Since my ActorState struct already implemented comparison functions, I just needed to add IEquatable as a part of its declaration.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace EstrellaLib.Actor
{
    public struct ActorState : IEquatable<ActorState>
    {
        string _stateName;

        public ActorState(string stateName)
        {
            _stateName = stateName.ToUpperInvariant();
        }

        public string StateName
        {
            get
            {
                return _stateName;
            }
            set
            {
                _stateName = value.ToUpperInvariant();
            }
        }

        public override bool Equals(object obj)
        {
            if (obj is ActorState)
            {
                return this.Equals((ActorState)obj);
            }
            return false;
        }

        public bool Equals(ActorState p)
        {
            return _stateName == p._stateName;
        }

        public override int GetHashCode()
        {
            return _stateName.GetHashCode();
        }

        public static bool operator ==(ActorState lhs, ActorState rhs)
        {
            return lhs.Equals(rhs);
        }

        public static bool operator !=(ActorState lhs, ActorState rhs)
        {
            return !(lhs.Equals(rhs));
        }

        public static ActorState None = new ActorState("NONE");
    }
}

Once I did that, ActorState disappeared from my allocation list on a second CLRProfiler run.

Fixing Problem #2: Value Type Boxing with PropertyInfos
While the boxing problem caused by my code is relatively straight-forward:

        public override void ApplyToActor(Actor targetActor)
        {
            if (_targetProperty == null)
            {
                _targetProperty = targetActor.GetType().GetProperty(_targetName);
                _targetActor = targetActor;
            }
            System.Diagnostics.Debug.Assert(_targetActor == targetActor);

            //PropertyInfo.SetValue(object obj, object value, object[] index)'s second parameter
            // causes any value types I pass to it to be boxed in a temporary System.Object
            _targetProperty.SetValue(targetActor, _targetValue, null);
        }

Fixing it, is not quite as easy as simply implementing a particular interface. I need to find a way to avoid calling SetValue(). Thankfully, PropertyInfo provides a way for me to get a delegate for setting the properties value that is typed to the value I’m trying to set. This means that, if I go through the setter delegate, I can avoid implicitly casting my value type to a System.Object and creating garbage. With a bit of work, I modify my code and end up with something like this:

        public override void ApplyToActor(Actor targetActor)
        {
            if (_targetActor == null)
            {
                PropertyInfo targetProperty = targetActor.GetType().GetProperty(_targetName);
                _targetActor = targetActor;

                MethodInfo setter = targetProperty.GetSetMethod();
                System.Diagnostics.Debug.Assert(setter != null);
                _propertySetter = (Action<T>)Delegate.CreateDelegate(typeof(Action<T>), targetActor, setter);
            }
            System.Diagnostics.Debug.Assert(_targetActor == targetActor);
            System.Diagnostics.Debug.Assert(_propertySetter != null);
            _propertySetter(_targetValue);
        }

It’s not as easy to read as the version using SetValue, but it doesn’t produce any garbage. For a function that will be called multiple times a frame, it’s totally worth it.

Enjoying the Fruits of my Labor
With my code changes in place, I fire up CLRProfiler again to make sure I get the expected effect. The GC Timeline ends up looking something like this.

There we go! A few GCs occurring when the game starts up, but once we get to the Title screen: nothing. Just a flat plane of constant memory use. My garbage collection problems are solved! At least in this part of the game.

Share this Article:
  • Digg
  • StumbleUpon
  • del.icio.us
  • Facebook
  • Yahoo! Buzz
  • Twitter
  • Google Bookmarks
  • Print