The Big Performance Danger of Value Types (Structs)

I talk to many folks who are eager to use structs because of the great performance advantages. While it is true that a struct is much quicker to allocate than a reference type (class) (somewhere around an order of magnitude quicker if all the member variables are also value types), structs can have some very serious performance problems. These include:

  • When passed as a parameter, the memory of a value type need to be copied onto the stack, and if a very large value type is created, the cost of this copying can be significant. Value types should ideally by around ~20 bytes in size, with ~100 bytes being a general ceiling.
  • A call stack is only so big, and if you have very big value types passed as parameters, you'll end up with a stack overflow exception.
  • The big gotcha of value types is their equality and hashcode implementations. If a value type only contains other value types, all is sweet, as a bitwise equality check and simple hashcode calculations can be used. If a value type contains reference types, things get ugly. If two value types have member variables that are reference types, and these fields have different values (i.e. the pointer that points to the reference type is different), it is possible that these reference types will have overridden their equality operator to define equality in terms of member variable sameness (System.String is the prime example of this). Therefore, value types that have reference type member variables can be Equal (because all their member variables are Equal) even if they have different bits. To accommodate this scenario, a value type that has reference type member variables needs to use reflection (which is massively slower than bitwise equality) to do the equality checks on each member variable. Because objects that are Equal must have the same hashcode, GetHashCode has the same performance problems as Equals for value types with reference types.

    The end result of this is that you should always override Equals and GetHashCode on publicly visible value types if you have reference type member variable. FxCop goes further, and uses a dumbed-down (but justifiably cautious) explanation that states reflection is always used for value type equality without an explicit override. FxCop is a great tool for picking up these performance issues, and keeping an eye on its output is a good idea during the course of a development cycle.
    Posted: Jul 28 2006, 02:53 AM by nick | with 2 comment(s)
    Filed under:
  • Comments

    David M. Kean said:

    Nick,

    You're right about the FxCop documentation; it is wrong in stipulating that ValueType.Equals always uses Reflection, and I will file a bug on it tomorrow.

    You are also more likely to see more FxCop rules targetting value types in the future, particularly around their size (Design Guidelines state around 16 bytes), and their immutability.

    Regards

    David


    # July 27, 2006 10:57 PM

    Nick Wienholt said:

    Hi David,

    Great stuff - thanks for listening.

    Nick
    # July 28, 2006 12:55 AM