Improbable Icon

SpatialOS Codegen flag schema to be IEquatable<>

Hello fellow Spatial devs - I’ve been looking into some Heap memory allocation issues in our project and figured out a solution that involves altering the structs generated by the SpatialOS Code Generator.

[global::System.Serializable]
public struct Vector3f : IEquatable
{
public float X;
public float Y;
public float Z;

    public Vector3f(float x, float y, float z)
    {
        X = x;
        Y = y;
        Z = z;
    }

    public override bool Equals(object obj) =>
        (obj is Vector3f metrics) && Equals(metrics);

    public bool Equals(Vector3f other) =>
        (X, Y, Z) == (other.X, other.Y, other.Z);

    public override int GetHashCode() =>
         (X,Y, Z).GetHashCode();

}

This would be the kind of struct I’d want to get to - for now I have just edited the generated struct after the fact.

I need the struct to be IEquatable because I’m using Dictionary.ContainsKey(Veftor3f) which allocates a lot of memory into the heap if the structs used as Keys are not IEquatable.

This is based off information from this article:

Memory Allocation Optimization Article

Where he compares searching for struct keys in dictionaries:

// dict is a tiny Dictionary with just one entry
// 128K calls to dict.ContainsKey()
// Whitout using IEquatable<>

SmallStruct … 2.0 MB … 20 ms
LargeStruct … 11.0 MB … 52 ms

// 128K calls to dict.ContainsKey(SmallStruct)

plain … 2.0 MB … 20 ms
IEquatable< T > … 2.0 MB … 20 ms
Equals(), GetHashCode() … 2.0 MB … 14 ms
IEquatable T, Equals(), GetHashCode() … 0 B … 2 ms

// 128K calls to dict.ContainsKey(LargeStruct)

plain … 11.0 MB … 52 ms
IEquatable< T > … 11.0 MB … 52 ms
Equals(), GetHashCode() … 11.0 MB … 38 ms
IEquatable T, Equals(), GetHashCode() … 0 B … 13 ms

So I’d need a way to flag some of my schemas (all of which are used in collections such as dicts) to be generated as IEquatable. As it stands the code generator will override my changes to these structs.

I’ve read the custom code Generator Documentation but can’t really wrap my head around how I’m supposed to tackle this problem so any help would be greatly appreciated.

The main reason is performance. When generics were introduced in .NET 2.0 they were able to add a bunch of neat classes such as List, Dictionary<K,V>, HashSet, etc. These structures make heavy use of GetHashCode and Equals. But for value types this required boxing. IEquatable lets a structure implement a strongly typed Equals method so no boxing is required. Thus much better performance when using value types with generic collections.

Reference types don’t benefit as much but the IEquatable implementation does let you avoid a cast from System.Object which can make a difference if it’s called frequently.

As noted on Jared Parson’s blog though, you must still implement the standard Object.Equals and Object.GetHashcode overrides.