Skip to content

Instantly share code, notes, and snippets.

@laicasaane
Last active December 6, 2021 09:22
Show Gist options
  • Save laicasaane/126b6e3104f29e6828e16611c27e1e0f to your computer and use it in GitHub Desktop.
Save laicasaane/126b6e3104f29e6828e16611c27e1e0f to your computer and use it in GitHub Desktop.
Test GC Alloc for struct to interface casting
using System;
using UnityEngine;
namespace Examples
{
public partial class ExampleBehaviour : MonoBehaviour
{
private static readonly UnityEngine.Profiling.Recorder s_recorder;
static ExampleBehaviour()
{
s_recorder = UnityEngine.Profiling.Recorder.Get("GC.Alloc");
}
private static int CountGCAllocs(Action action)
{
s_recorder.FilterToCurrentThread();
s_recorder.enabled = false;
s_recorder.enabled = true;
action();
s_recorder.enabled = false;
return s_recorder.sampleBlockCount;
}
private void Start()
{
EGIDSetter<ComponentA>.Warmup();
CountGCAllocs(SetIDWithoutCasting);
var count = CountGCAllocs(SetIDWithoutCasting);
Debug.Log($"Set Without Casting: {count}");
count = CountGCAllocs(SetIDWithCasting);
Debug.Log($"Set With Casting: {count}");
}
private void SetIDWithoutCasting()
{
var comp = new ComponentA();
for (var i = 1; i <= 1_000_000; i++)
{
var id = new EGID(i);
EGIDSetter<ComponentA>.SetIDWithoutCasting(ref comp, id);
}
}
private void SetIDWithCasting()
{
var comp = new ComponentA();
for (var i = 1; i <= 1_000_000; i++)
{
var id = new EGID(i);
EGIDSetter<ComponentA>.SetIDWithCasting(ref comp, id);
}
}
private struct ComponentA : IEntityComponent, INeedEGID
{
public EGID ID { get; set; }
public override string ToString()
=> ID.ToString();
}
}
public struct EGID
{
private readonly int _value;
public EGID(int value)
=> _value = value;
public override string ToString()
=> _value.ToString();
}
public interface IEntityComponent { }
public interface INeedEGID
{
EGID ID { get; set; }
}
delegate void SetEGIDAction<T>(ref T target, EGID egid) where T : struct, IEntityComponent;
static class EGIDSetter<T> where T : struct, IEntityComponent
{
public static readonly SetEGIDAction<T> SetIDWithoutCasting = MakeSetter();
public static void Warmup() { }
static SetEGIDAction<T> MakeSetter()
{
var method = typeof(Trick).GetMethod(nameof(Trick.SetIDWihoutCasting)).MakeGenericMethod(typeof(T));
return (SetEGIDAction<T>)System.Delegate.CreateDelegate(typeof(SetEGIDAction<T>), method);
}
public static void SetIDWithCasting(ref T target, EGID egid)
{
if (target is INeedEGID needEGID)
{
needEGID.ID = egid;
target = (T)needEGID;
}
}
static class Trick
{
public static void SetIDWihoutCasting<U>(ref U target, EGID egid) where U : struct, INeedEGID
{
target.ID = egid;
}
}
}
}
@laicasaane
Copy link
Author

laicasaane commented Dec 5, 2021

The result of this method clearly show that casting a struct to interface will incur boxing at runtime.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment