diff --git a/Content.Benchmarks/ComponentFetchBenchmark.cs b/Content.Benchmarks/ComponentFetchBenchmark.cs new file mode 100644 index 0000000000..a845670de1 --- /dev/null +++ b/Content.Benchmarks/ComponentFetchBenchmark.cs @@ -0,0 +1,257 @@ +using System; +using System.Collections.Generic; +using BenchmarkDotNet.Attributes; +using Robust.Shared.Utility; + +namespace Content.Benchmarks +{ + [SimpleJob] + public class ComponentFetchBenchmark + { + [Params(5000)] public int NEnt { get; set; } + + private readonly Dictionary<(EntityUid, Type), BComponent> + _componentsFlat = new Dictionary<(EntityUid, Type), BComponent>(); + + private readonly Dictionary> _componentsPart = + new Dictionary>(); + + private UniqueIndex _allComponents = new UniqueIndex(); + + private readonly List _lookupEntities = new List(); + + [GlobalSetup] + public void Setup() + { + var random = new Random(); + + _componentsPart[typeof(BComponent1)] = new Dictionary(); + _componentsPart[typeof(BComponent2)] = new Dictionary(); + _componentsPart[typeof(BComponent3)] = new Dictionary(); + _componentsPart[typeof(BComponent4)] = new Dictionary(); + _componentsPart[typeof(BComponentLookup)] = new Dictionary(); + _componentsPart[typeof(BComponent6)] = new Dictionary(); + _componentsPart[typeof(BComponent7)] = new Dictionary(); + _componentsPart[typeof(BComponent8)] = new Dictionary(); + _componentsPart[typeof(BComponent9)] = new Dictionary(); + + for (var i = 0u; i < NEnt; i++) + { + var eId = new EntityUid(i); + + if (random.Next(1) == 0) + { + _lookupEntities.Add(eId); + } + + var comps = new List + { + new BComponent1(), + new BComponent2(), + new BComponent3(), + new BComponent4(), + new BComponent6(), + new BComponent7(), + new BComponent8(), + new BComponent9(), + }; + + if (random.Next(1000) == 0) + { + comps.Add(new BComponentLookup()); + } + + foreach (var comp in comps) + { + comp.Uid = eId; + var type = comp.GetType(); + _componentsPart[type][eId] = comp; + _componentsFlat[(eId, type)] = comp; + _allComponents.Add(type, comp); + } + } + } + + // These two benchmarks are find "needles in haystack" components. + // We try to look up a component that 0.1% of entities have on 1% of entities. + // Examples of this in the engine are VisibilityComponent lookups during PVS. + [Benchmark] + public void FindPart() + { + foreach (var entityUid in _lookupEntities) + { + var d = _componentsPart[typeof(BComponentLookup)]; + d.TryGetValue(entityUid, out _); + } + } + + [Benchmark] + public void FindFlat() + { + foreach (var entityUid in _lookupEntities) + { + _componentsFlat.TryGetValue((entityUid, typeof(BComponentLookup)), out _); + } + } + + // Iteration benchmarks: + // We try to iterate every instance of a single component (BComponent1) and see which is faster. + [Benchmark] + public void IterPart() + { + var list = _componentsPart[typeof(BComponent1)]; + var arr = new BComponent[list.Count]; + var i = 0; + foreach (var c in list.Values) + { + arr[i++] = c; + } + } + + [Benchmark] + public void IterFlat() + { + var list = _allComponents[typeof(BComponent1)]; + var arr = new BComponent[list.Count]; + var i = 0; + foreach (var c in list) + { + arr[i++] = c; + } + } + + // We do the same as the iteration benchmarks but re-fetch the component every iteration. + // This is what entity systems mostly do via entity queries because crappy code. + [Benchmark] + public void IterFetchPart() + { + var list = _componentsPart[typeof(BComponent1)]; + var arr = new BComponent[list.Count]; + var i = 0; + foreach (var c in list.Values) + { + var eId = c.Uid; + var d = _componentsPart[typeof(BComponent1)]; + arr[i++] = d[eId]; + } + } + + [Benchmark] + public void IterFetchFlat() + { + var list = _allComponents[typeof(BComponent1)]; + var arr = new BComponent[list.Count]; + var i = 0; + foreach (var c in list) + { + var eId = c.Uid; + arr[i++] = _componentsFlat[(eId, typeof(BComponent1))]; + } + } + + // Same as the previous benchmarks but with BComponentLookup instead. + // Which is only on 1% of entities. + [Benchmark] + public void IterFetchPartRare() + { + var list = _componentsPart[typeof(BComponentLookup)]; + var arr = new BComponent[list.Count]; + var i = 0; + foreach (var c in list.Values) + { + var eId = c.Uid; + var d = _componentsPart[typeof(BComponentLookup)]; + arr[i++] = d[eId]; + } + } + + [Benchmark] + public void IterFetchFlatRare() + { + var list = _allComponents[typeof(BComponentLookup)]; + var arr = new BComponent[list.Count]; + var i = 0; + foreach (var c in list) + { + var eId = c.Uid; + arr[i++] = _componentsFlat[(eId, typeof(BComponentLookup))]; + } + } + + private readonly struct EntityUid : IEquatable + { + public readonly uint Value; + + public EntityUid(uint value) + { + Value = value; + } + + public bool Equals(EntityUid other) + { + return Value == other.Value; + } + + public override bool Equals(object obj) + { + return obj is EntityUid other && Equals(other); + } + + public override int GetHashCode() + { + return (int) Value; + } + + public static bool operator ==(EntityUid left, EntityUid right) + { + return left.Equals(right); + } + + public static bool operator !=(EntityUid left, EntityUid right) + { + return !left.Equals(right); + } + } + + private abstract class BComponent + { + public EntityUid Uid; + } + + private class BComponent1 : BComponent + { + } + + private class BComponent2 : BComponent + { + } + + private class BComponent3 : BComponent + { + } + + private class BComponent4 : BComponent + { + } + + private class BComponentLookup : BComponent + { + } + + private class BComponent6 : BComponent + { + } + + private class BComponent7 : BComponent + { + } + + private class BComponent8 : BComponent + { + } + + private class BComponent9 : BComponent + { + } + } +} diff --git a/Content.Benchmarks/Program.cs b/Content.Benchmarks/Program.cs index 74ee4e265b..187c328127 100644 --- a/Content.Benchmarks/Program.cs +++ b/Content.Benchmarks/Program.cs @@ -6,8 +6,7 @@ namespace Content.Benchmarks { public static void Main(string[] args) { - BenchmarkRunner.Run(); - //ComponentManagerGetAllComponents.TestRun(); + BenchmarkSwitcher.FromAssembly(typeof(Program).Assembly).Run(args); } } }