Files
tbd-station-14/Content.Benchmarks/ComponentFetchBenchmark.cs
2022-02-16 18:23:23 +11:00

258 lines
7.6 KiB
C#

using System;
using System.Collections.Generic;
using BenchmarkDotNet.Attributes;
using Robust.Shared.Utility;
namespace Content.Benchmarks
{
[SimpleJob]
public sealed class ComponentFetchBenchmark
{
[Params(5000)] public int NEnt { get; set; }
private readonly Dictionary<(EntityUid, Type), BComponent>
_componentsFlat = new();
private readonly Dictionary<Type, Dictionary<EntityUid, BComponent>> _componentsPart =
new();
private UniqueIndex<Type, BComponent> _allComponents = new();
private readonly List<EntityUid> _lookupEntities = new();
[GlobalSetup]
public void Setup()
{
var random = new Random();
_componentsPart[typeof(BComponent1)] = new Dictionary<EntityUid, BComponent>();
_componentsPart[typeof(BComponent2)] = new Dictionary<EntityUid, BComponent>();
_componentsPart[typeof(BComponent3)] = new Dictionary<EntityUid, BComponent>();
_componentsPart[typeof(BComponent4)] = new Dictionary<EntityUid, BComponent>();
_componentsPart[typeof(BComponentLookup)] = new Dictionary<EntityUid, BComponent>();
_componentsPart[typeof(BComponent6)] = new Dictionary<EntityUid, BComponent>();
_componentsPart[typeof(BComponent7)] = new Dictionary<EntityUid, BComponent>();
_componentsPart[typeof(BComponent8)] = new Dictionary<EntityUid, BComponent>();
_componentsPart[typeof(BComponent9)] = new Dictionary<EntityUid, BComponent>();
for (var i = 0u; i < NEnt; i++)
{
var eId = new EntityUid(i);
if (random.Next(1) == 0)
{
_lookupEntities.Add(eId);
}
var comps = new List<BComponent>
{
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<EntityUid>
{
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 sealed class BComponent1 : BComponent
{
}
private sealed class BComponent2 : BComponent
{
}
private sealed class BComponent3 : BComponent
{
}
private sealed class BComponent4 : BComponent
{
}
private sealed class BComponentLookup : BComponent
{
}
private sealed class BComponent6 : BComponent
{
}
private sealed class BComponent7 : BComponent
{
}
private sealed class BComponent8 : BComponent
{
}
private sealed class BComponent9 : BComponent
{
}
}
}